diff --git a/.github/workflows/build_check.yaml b/.github/workflows/build_check.yaml index e06a11aba..4038ad366 100644 --- a/.github/workflows/build_check.yaml +++ b/.github/workflows/build_check.yaml @@ -51,7 +51,7 @@ jobs: uses: actions/upload-artifact@v1 with: name: apk - path: animeworld/build/outputs/apk/debug/animeworld-debug.apk + path: animeworld/build/outputs/apk/debug/animeworld-debug.apk - name: Upload APK uses: actions/upload-artifact@v1 with: diff --git a/.idea/misc.xml b/.idea/misc.xml index 0bd14c3d3..24b1ecc23 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -27,6 +27,7 @@ + @@ -43,6 +44,7 @@ + @@ -59,6 +61,7 @@ + diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/AllFragment.kt b/UIViews/src/main/java/com/programmersbox/uiviews/AllFragment.kt index 40632ccd8..483bbc58e 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/AllFragment.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/AllFragment.kt @@ -1,159 +1,278 @@ package com.programmersbox.uiviews import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.slideInVertically +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Cancel +import androidx.compose.material.icons.filled.CloudOff +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.runtime.* +import androidx.compose.runtime.rxjava2.subscribeAsState +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastMaxBy import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView +import androidx.navigation.fragment.findNavController import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.SwipeRefreshState +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.android.material.composethemeadapter.MdcTheme -import com.jakewharton.rxbinding2.widget.textChanges -import com.programmersbox.dragswipe.DragSwipeAdapter -import com.programmersbox.dragswipe.DragSwipeDiffUtil import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.favoritesdatabase.ItemDatabase -import com.programmersbox.helpfulutils.gone -import com.programmersbox.helpfulutils.runOnUIThread -import com.programmersbox.helpfulutils.visible import com.programmersbox.models.ApiService import com.programmersbox.models.ItemModel import com.programmersbox.models.sourcePublish import com.programmersbox.sharedutils.FirebaseDb -import com.programmersbox.uiviews.databinding.FragmentAllBinding -import com.programmersbox.uiviews.utils.EndlessScrollingListener +import com.programmersbox.uiviews.utils.InfiniteListHandler import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.Flowables import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.delay +import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.launch -import java.util.concurrent.TimeUnit +import org.koin.android.ext.android.inject /** * A simple [Fragment] subclass. * Use the [AllFragment.newInstance] factory method to * create an instance of this fragment. */ -class AllFragment : BaseListFragment() { +class AllFragment : BaseFragmentCompose() { private val disposable: CompositeDisposable = CompositeDisposable() private var count = 1 - override val layoutId: Int get() = R.layout.fragment_all + private val info: GenericInfo by inject() - private val currentList = mutableListOf() + private val searchPublisher = BehaviorSubject.createDefault>(emptyList()) + + private val sourceList = mutableStateListOf() + private val favoriteList = mutableStateListOf() private val dao by lazy { ItemDatabase.getInstance(requireContext()).itemDao() } private val itemListener = FirebaseDb.FirebaseListener() - private lateinit var binding: FragmentAllBinding + @ExperimentalAnimationApi + @ExperimentalFoundationApi + @ExperimentalMaterialApi + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = ComposeView(requireContext()) + .apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { AllView() } + } override fun viewCreated(view: View, savedInstanceState: Bundle?) { - super.viewCreated(view, savedInstanceState) - - binding = FragmentAllBinding.bind(view) + sourcePublish + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + count = 1 + sourceList.clear() + searchPublisher.onNext(emptyList()) + sourceLoadCompose(it) + } + .addTo(disposable) Flowables.combineLatest( itemListener.getAllShowsFlowable(), dao.getAllFavorites() ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { adapter.update(it) { s, d -> s.url == d.url } } - .addTo(disposable) - - binding.allList.apply { - adapter = this@AllFragment.adapter - layoutManager = info.createLayoutManager(this@AllFragment.requireContext()) - addOnScrollListener(object : EndlessScrollingListener(layoutManager!!) { - override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) { - if (sourcePublish.value!!.canScroll && binding.searchInfo.text.isNullOrEmpty()) { - count++ - binding.allRefresh.isRefreshing = true - sourceLoad(sourcePublish.value!!, count) - } - } - }) - } - - binding.composeShimmer.setContent { MdcTheme { info.ComposeShimmerItem() } } - - ReactiveNetwork.observeInternetConnectivity() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { - binding.offlineView.visibility = if (it) View.GONE else View.VISIBLE - binding.allRefresh.visibility = if (it) View.VISIBLE else View.GONE + favoriteList.clear() + favoriteList.addAll(it) } .addTo(disposable) + } - sourcePublish + private fun sourceLoadCompose(sources: ApiService, page: Int = 1, refreshState: SwipeRefreshState? = null) { + sources + .getList(page) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { - binding.composeShimmer.visible() - count = 1 - adapter.setListNotify(emptyList()) - sourceLoad(it) - binding.allList.scrollToPosition(0) + .doOnSubscribe { refreshState?.isRefreshing = true } + .subscribeBy { + sourceList.addAll(it) + refreshState?.isRefreshing = false } .addTo(disposable) + } - binding.scrollToTop.setOnClickListener { - lifecycleScope.launch { - activity?.runOnUiThread { binding.allList.smoothScrollToPosition(0) } - delay(500) - activity?.runOnUiThread { binding.allList.scrollToPosition(0) } - } - } + @ExperimentalAnimationApi + @ExperimentalMaterialApi + @ExperimentalFoundationApi + @Composable + private fun AllView() { + MdcTheme { + val isConnected by ReactiveNetwork.observeInternetConnectivity() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeAsState(initial = true) - binding.searchInfo - .textChanges() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .debounce(1000, TimeUnit.MILLISECONDS) - .flatMapSingle { sourcePublish.value!!.searchList(it, 1, currentList) } - .onErrorReturnItem(currentList) - .subscribe { - adapter.setData(it) - activity?.runOnUiThread { binding.searchLayout.suffixText = "${it.size}" } - } - .addTo(disposable) + when { + !isConnected -> { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + Icons.Default.CloudOff, + null, + modifier = Modifier.size(50.dp, 50.dp), + colorFilter = ColorFilter.tint(MaterialTheme.colors.onBackground) + ) + Text(stringResource(R.string.you_re_offline), style = MaterialTheme.typography.h5) + } + } + else -> { + val state = rememberLazyListState() + val refresh = rememberSwipeRefreshState(isRefreshing = false) + val source by sourcePublish.subscribeAsState(initial = null) + val focusManager = LocalFocusManager.current + val scaffoldState = rememberBottomSheetScaffoldState() + val scope = rememberCoroutineScope() + val searchList by searchPublisher.subscribeAsState(initial = emptyList()) + var searchText by rememberSaveable { mutableStateOf("") } + val showButton by remember { derivedStateOf { state.firstVisibleItemIndex > 0 } } - } + BottomSheetScaffold( + scaffoldState = scaffoldState, + sheetPeekHeight = ButtonDefaults.MinHeight + 4.dp, + sheetContent = { + Scaffold( + topBar = { + Column { + Button( + onClick = { + scope.launch { + if (scaffoldState.bottomSheetState.isCollapsed) scaffoldState.bottomSheetState.expand() + else scaffoldState.bottomSheetState.collapse() + } + }, + modifier = Modifier + .fillMaxWidth() + .heightIn(ButtonDefaults.MinHeight + 4.dp), + shape = RoundedCornerShape(0f) + ) { + Text( + stringResource(R.string.search), + style = MaterialTheme.typography.button + ) + } - private fun DragSwipeAdapter.setData(newList: List) { - val diffCallback = object : DragSwipeDiffUtil(dataList, newList) { - override fun areContentsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean = oldItem.url == newItem.url - override fun areItemsTheSame(oldItem: ItemModel, newItem: ItemModel): Boolean = oldItem.url === newItem.url - } - val diffResult = DiffUtil.calculateDiff(diffCallback) - dataList.clear() - dataList.addAll(newList) - runOnUIThread { diffResult.dispatchUpdatesTo(this) } - } + OutlinedTextField( + value = searchText, + onValueChange = { searchText = it }, + label = { Text(stringResource(R.string.searchFor, source?.serviceName.orEmpty())) }, + trailingIcon = { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(searchList.size.toString()) + IconButton(onClick = { searchText = "" }) { Icon(Icons.Default.Cancel, null) } + } + }, + modifier = Modifier + .padding(5.dp) + .fillMaxWidth(), + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + keyboardActions = KeyboardActions(onSearch = { + focusManager.clearFocus() + sourcePublish.value!!.searchList(searchText, 1, sourceList) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .onErrorReturnItem(sourceList) + .subscribe(searchPublisher::onNext) + .addTo(disposable) + }) + ) + } + } + ) { p -> + Box(modifier = Modifier.padding(p)) { + info.ItemListView(list = searchList, listState = rememberLazyListState(), favorites = favoriteList) { + findNavController().navigate(AllFragmentDirections.actionAllFragment2ToDetailsFragment3(it)) + } + } + } + }, + floatingActionButton = { + AnimatedVisibility( + visible = showButton && scaffoldState.bottomSheetState.isCollapsed, + enter = slideInVertically({ it / 2 }) + ) { + FloatingActionButton( + onClick = { scope.launch { state.animateScrollToItem(0) } }, + backgroundColor = MaterialTheme.colors.primary + ) { + Icon( + imageVector = Icons.Default.KeyboardArrowUp, + contentDescription = null, + modifier = Modifier.padding(5.dp), + ) + } + } + }, + floatingActionButtonPosition = FabPosition.End + ) { p -> + SwipeRefresh( + modifier = Modifier.padding(p), + state = refresh, + onRefresh = { + source?.let { + count = 1 + sourceList.clear() + sourceLoadCompose(it, count, refresh) + } + } + ) { + if (sourceList.isEmpty()) { + info.ComposeShimmerItem() + } else { + info.ItemListView(list = sourceList, listState = state, favorites = favoriteList) { + findNavController().navigate(AllFragmentDirections.actionAllFragment2ToDetailsFragment3(it)) + } + } + } - private fun sourceLoad(sources: ApiService, page: Int = 1) { - sources.getList(page) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeBy { - adapter.addItems(it) - currentList.clear() - currentList.addAll(it) - binding.allRefresh.isRefreshing = false - activity?.runOnUiThread { - binding.searchLayout.editText?.setText("") - binding.searchLayout.suffixText = "${adapter.dataList.size}" - binding.searchLayout.hint = getString(R.string.searchFor, sourcePublish.value?.serviceName.orEmpty()) + if (source?.canScroll == true) { + InfiniteListHandler(listState = state, buffer = 1) { + source?.let { + count++ + sourceLoadCompose(it, count, refresh) + } + } + } + + } } - binding.composeShimmer.gone() } - .addTo(disposable) + } } override fun onDestroy() { diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/BaseFragment.kt b/UIViews/src/main/java/com/programmersbox/uiviews/BaseFragment.kt index 33c949f93..e7ac4b5a1 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/BaseFragment.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/BaseFragment.kt @@ -41,5 +41,20 @@ abstract class BaseFragment : Fragment() { } } + abstract fun viewCreated(view: View, savedInstanceState: Bundle?) +} + +abstract class BaseFragmentCompose : Fragment() { + + private var hasInitializedRootView = false + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (!hasInitializedRootView) { + hasInitializedRootView = true + viewCreated(view, savedInstanceState) + } + } + abstract fun viewCreated(view: View, savedInstanceState: Bundle?) } \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/BaseListFragment.kt b/UIViews/src/main/java/com/programmersbox/uiviews/BaseListFragment.kt deleted file mode 100644 index fa5b3a2b9..000000000 --- a/UIViews/src/main/java/com/programmersbox/uiviews/BaseListFragment.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.programmersbox.uiviews - -import android.os.Bundle -import android.view.View -import androidx.annotation.CallSuper -import androidx.recyclerview.widget.RecyclerView -import org.koin.android.ext.android.inject - -abstract class BaseListFragment : BaseFragment() { - protected lateinit var adapter: ItemListAdapter - - protected val info: GenericInfo by inject() - - @CallSuper - override fun viewCreated(view: View, savedInstanceState: Bundle?) { - adapter = info.createAdapter(this@BaseListFragment.requireContext(), this) - } -} \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/GenericInfo.kt b/UIViews/src/main/java/com/programmersbox/uiviews/GenericInfo.kt index 633fdba27..e31c1a6ca 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/GenericInfo.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/GenericInfo.kt @@ -1,18 +1,18 @@ package com.programmersbox.uiviews import android.content.Context +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable -import androidx.recyclerview.widget.RecyclerView +import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.models.ApiService import com.programmersbox.models.ChapterModel +import com.programmersbox.models.ItemModel import com.programmersbox.sharedutils.AppUpdate interface GenericInfo { val apkString: AppUpdate.AppUpdates.() -> String? - fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter - fun createLayoutManager(context: Context): RecyclerView.LayoutManager fun chapterOnClick(model: ChapterModel, allChapters: List, context: Context) fun sourceList(): List fun searchList(): List = sourceList() @@ -23,4 +23,12 @@ interface GenericInfo { @Composable fun ComposeShimmerItem() + @Composable + fun ItemListView( + list: List, + favorites: List, + listState: LazyListState, + onClick: (ItemModel) -> Unit + ) + } \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/GlobalSearchFragment.kt b/UIViews/src/main/java/com/programmersbox/uiviews/GlobalSearchFragment.kt index 3607aa306..e8d27dcb8 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/GlobalSearchFragment.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/GlobalSearchFragment.kt @@ -6,7 +6,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image @@ -22,7 +25,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Cancel import androidx.compose.material.icons.filled.CloudOff -import androidx.compose.material.icons.filled.VerticalAlignTop +import androidx.compose.material.icons.filled.KeyboardArrowUp import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.rxjava2.subscribeAsState @@ -99,6 +102,7 @@ class GlobalSearchFragment : Fragment() { var searchText by rememberSaveable { mutableStateOf(args.searchFor) } val focusManager = LocalFocusManager.current val listState = rememberLazyListState() + val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } val scope = rememberCoroutineScope() val swipeRefreshState = rememberSwipeRefreshState(isRefreshing = false) val list by searchPublisher.subscribeAsState(initial = emptyList()) @@ -206,9 +210,15 @@ class GlobalSearchFragment : Fragment() { ) { Scaffold( floatingActionButton = { - FloatingActionButton( - onClick = { scope.launch { listState.animateScrollToItem(0) } } - ) { Icon(Icons.Default.VerticalAlignTop, null) } + AnimatedVisibility( + visible = showButton && searchText.isNotEmpty(), + enter = slideInVertically({ it / 2 }), + exit = slideOutVertically({ it / 2 }) + ) { + FloatingActionButton( + onClick = { scope.launch { listState.animateScrollToItem(0) } } + ) { Icon(Icons.Default.KeyboardArrowUp, null) } + } }, floatingActionButtonPosition = FabPosition.End ) { @@ -227,7 +237,7 @@ class GlobalSearchFragment : Fragment() { SearchCoverCard( model = m, placeHolder = AppCompatResources.getDrawable(LocalContext.current, mainLogo.logoId) - ) { findNavController().navigate(GlobalSearchFragmentDirections.showDetails(m)) } + ) { findNavController().navigate(GlobalNavDirections.showDetails(m)) } } } } @@ -251,20 +261,6 @@ class GlobalSearchFragment : Fragment() { } } } - - /*LaunchedEffect(key1 = searchText) { - snapshotFlow { searchText } - .debounce(1000) - .distinctUntilChanged() - .collect { s -> - println("Searching for $s") - searchForItems( - searchText = searchText, - onSubscribe = { swipeRefreshState.isRefreshing = true }, - subscribe = { swipeRefreshState.isRefreshing = false } - ) - } - }*/ } } } diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/ItemListAdapter.kt b/UIViews/src/main/java/com/programmersbox/uiviews/ItemListAdapter.kt deleted file mode 100644 index 732f822b8..000000000 --- a/UIViews/src/main/java/com/programmersbox/uiviews/ItemListAdapter.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.programmersbox.uiviews - -import android.content.Context -import android.view.View -import androidx.navigation.findNavController -import androidx.recyclerview.widget.RecyclerView -import com.programmersbox.dragswipe.CheckAdapter -import com.programmersbox.dragswipe.CheckAdapterInterface -import com.programmersbox.dragswipe.DragSwipeAdapter -import com.programmersbox.favoritesdatabase.DbModel -import com.programmersbox.models.ItemModel - -abstract class ItemListAdapter( - protected val context: Context, - private val baseListFragment: BaseListFragment, - check: CheckAdapter = CheckAdapter() -) : DragSwipeAdapter(), CheckAdapterInterface by check { - init { - check.adapter = this - } - - protected fun onClick(v: View, itemModel: ItemModel) { - val direction = if (baseListFragment is RecentFragment) RecentFragmentDirections.actionRecentFragment2ToDetailsFragment2(itemModel) - else AllFragmentDirections.actionAllFragment2ToDetailsFragment3(itemModel) - v.findNavController().navigate(direction) - } -} \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/RecentFragment.kt b/UIViews/src/main/java/com/programmersbox/uiviews/RecentFragment.kt index 48114092b..f65469bb7 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/RecentFragment.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/RecentFragment.kt @@ -1,138 +1,186 @@ package com.programmersbox.uiviews import android.os.Bundle +import android.view.LayoutInflater import android.view.View -import androidx.compose.runtime.* +import android.view.ViewGroup +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CloudOff +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.rxjava2.subscribeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastMaxBy import androidx.fragment.app.Fragment -import androidx.recyclerview.widget.RecyclerView +import androidx.navigation.fragment.findNavController import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.SwipeRefreshState +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.android.material.composethemeadapter.MdcTheme import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.favoritesdatabase.ItemDatabase -import com.programmersbox.helpfulutils.gone -import com.programmersbox.helpfulutils.visible import com.programmersbox.models.ApiService +import com.programmersbox.models.ItemModel import com.programmersbox.models.sourcePublish import com.programmersbox.sharedutils.FirebaseDb -import com.programmersbox.uiviews.databinding.FragmentRecentBinding -import com.programmersbox.uiviews.utils.EndlessScrollingListener +import com.programmersbox.uiviews.utils.InfiniteListHandler import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.Flowables import io.reactivex.rxkotlin.addTo import io.reactivex.rxkotlin.subscribeBy import io.reactivex.schedulers.Schedulers +import org.koin.android.ext.android.inject /** * A simple [Fragment] subclass. * Use the [RecentFragment.newInstance] factory method to * create an instance of this fragment. */ -class RecentFragment : BaseListFragment() { +class RecentFragment : BaseFragmentCompose() { - override val layoutId: Int get() = R.layout.fragment_recent + private val info: GenericInfo by inject() private val disposable: CompositeDisposable = CompositeDisposable() + private val sourceList = mutableStateListOf() + private val favoriteList = mutableStateListOf() private var count = 1 private val dao by lazy { ItemDatabase.getInstance(requireContext()).itemDao() } private val itemListener = FirebaseDb.FirebaseListener() - private lateinit var binding: FragmentRecentBinding - - override fun viewCreated(view: View, savedInstanceState: Bundle?) { - super.viewCreated(view, savedInstanceState) - binding = FragmentRecentBinding.bind(view) - - Flowables.combineLatest( - itemListener.getAllShowsFlowable(), - dao.getAllFavorites() - ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { adapter.update(it) { s, d -> s.url == d.url } } - .addTo(disposable) + companion object { + @JvmStatic + fun newInstance() = RecentFragment() + } - binding.recentList.apply { - adapter = this@RecentFragment.adapter - layoutManager = info.createLayoutManager(this@RecentFragment.requireContext()) - addOnScrollListener(object : EndlessScrollingListener(layoutManager!!) { - override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) { - if (sourcePublish.value!!.canScroll) { - count++ - binding.recentRefresh.isRefreshing = true - sourceLoad(sourcePublish.value!!, count) - } - } - }) + @ExperimentalFoundationApi + @ExperimentalMaterialApi + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = ComposeView(requireContext()) + .apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) + setContent { RecentView() } } - ReactiveNetwork.observeInternetConnectivity() + override fun viewCreated(view: View, savedInstanceState: Bundle?) { + sourcePublish .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { - binding.offlineView.visibility = if (it) View.GONE else View.VISIBLE - binding.recentRefresh.visibility = if (it) View.VISIBLE else View.GONE + count = 1 + sourceList.clear() + sourceLoadCompose(it) } .addTo(disposable) - binding.recentRefresh.setOnRefreshListener { - binding.composeShimmer.visible() - count = 1 - adapter.setListNotify(emptyList()) - sourceLoad(sourcePublish.value!!) - binding.recentList.scrollToPosition(0) - } - - binding.composeShimmer.apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)) - setContent { MdcTheme { info.ComposeShimmerItem() } } - //TODO: right now, when going to the detail screen from recent, this disposes - //TODO: ...which means that when it shows up again, it wont compose again - //TODO: found out what's causing the compose not reloading issue. - //TODO: Its due to the BaseFragment and how it doesn't get started again. This is an issue. - //TODO: It is needed so the api calls don't get refreshed when returning to the screen. - //TODO: We will see how we can fix this going forward. - } - - sourcePublish + Flowables.combineLatest( + itemListener.getAllShowsFlowable(), + dao.getAllFavorites() + ) { f, d -> (f + d).groupBy(DbModel::url).map { it.value.fastMaxBy(DbModel::numChapters)!! } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { - binding.composeShimmer.visible() - count = 1 - adapter.setListNotify(emptyList()) - sourceLoad(it) - binding.recentList.scrollToPosition(0) + favoriteList.clear() + favoriteList.addAll(it) } .addTo(disposable) + } + override fun onDestroy() { + super.onDestroy() + disposable.dispose() + itemListener.unregister() } - private fun sourceLoad(sources: ApiService, page: Int = 1) { + private fun sourceLoadCompose(sources: ApiService, page: Int = 1, refreshState: SwipeRefreshState? = null) { sources .getRecent(page) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) + .doOnSubscribe { refreshState?.isRefreshing = true } .subscribeBy { - adapter.addItems(it) - binding.recentRefresh.isRefreshing = false - binding.composeShimmer.gone() + sourceList.addAll(it) + refreshState?.isRefreshing = false } .addTo(disposable) } - override fun onDestroy() { - super.onDestroy() - disposable.dispose() - itemListener.unregister() - } + @ExperimentalMaterialApi + @ExperimentalFoundationApi + @Composable + private fun RecentView() { + MdcTheme { + val state = rememberLazyListState() + val source by sourcePublish.subscribeAsState(initial = null) + val refresh = rememberSwipeRefreshState(isRefreshing = false) + + val isConnected by ReactiveNetwork.observeInternetConnectivity() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeAsState(initial = true) + + when { + !isConnected -> { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Image( + Icons.Default.CloudOff, + null, + modifier = Modifier.size(50.dp, 50.dp), + colorFilter = ColorFilter.tint(MaterialTheme.colors.onBackground) + ) + Text(stringResource(R.string.you_re_offline), style = MaterialTheme.typography.h5) + } + } + sourceList.isEmpty() -> info.ComposeShimmerItem() + else -> { + SwipeRefresh( + state = refresh, + onRefresh = { + source?.let { + count = 1 + sourceList.clear() + sourceLoadCompose(it, count, refresh) + } + } + ) { + info.ItemListView(list = sourceList, listState = state, favorites = favoriteList) { + findNavController().navigate(RecentFragmentDirections.actionRecentFragment2ToDetailsFragment2(it)) + } + } - companion object { - @JvmStatic - fun newInstance() = RecentFragment() + if (source?.canScroll == true) { + InfiniteListHandler(listState = state, buffer = 1) { + source?.let { + count++ + sourceLoadCompose(it, count, refresh) + } + } + } + } + } + } } } \ No newline at end of file diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/utils/BindingUtils.kt b/UIViews/src/main/java/com/programmersbox/uiviews/utils/BindingUtils.kt index 5dc67427c..1f28092ec 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/utils/BindingUtils.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/utils/BindingUtils.kt @@ -1,136 +1,24 @@ package com.programmersbox.uiviews.utils import android.animation.ValueAnimator -import android.content.res.ColorStateList import android.graphics.* import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable -import android.widget.CheckBox -import android.widget.ImageView -import android.widget.TextView import androidx.annotation.ColorInt import androidx.annotation.NonNull -import androidx.appcompat.widget.Toolbar import androidx.core.graphics.drawable.toBitmap -import androidx.databinding.BindingAdapter import androidx.palette.graphics.Palette import androidx.recyclerview.widget.RecyclerView import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieProperty import com.airbnb.lottie.model.KeyPath -import com.bumptech.glide.Glide import com.bumptech.glide.ListPreloader import com.bumptech.glide.RequestBuilder -import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition -import com.google.android.material.appbar.CollapsingToolbarLayout -import com.google.android.material.button.MaterialButton -import com.google.android.material.chip.Chip -import com.google.android.material.chip.ChipGroup import com.programmersbox.dragswipe.DragSwipeAdapter -import com.programmersbox.helpfulutils.changeDrawableColor -import com.programmersbox.models.ChapterModel -import com.programmersbox.models.SwatchInfo import kotlin.properties.Delegates -@BindingAdapter("coverImage", "logoId") -fun loadImage(view: ImageView, imageUrl: String?, logoId: Int) { - Glide.with(view) - .load(imageUrl) - .override(360, 480) - .placeholder(logoId) - .error(logoId) - .fallback(logoId) - .transform(RoundedCorners(15)) - .into(view) -} - -@BindingAdapter("otherNames") -fun otherNames(view: TextView, names: List?) { - view.text = names?.joinToString("\n\n") -} - -@BindingAdapter("genreList", "swatch") -fun loadGenres(view: ChipGroup, genres: List?, swatchInfo: SwatchInfo?) { - view.removeAllViews() - genres?.forEach { - view.addView(Chip(view.context).apply { - text = it - isCheckable = false - isClickable = false - swatchInfo?.rgb?.let { setTextColor(it) } - swatchInfo?.bodyColor?.let { chipBackgroundColor = ColorStateList.valueOf(it) } - }) - } -} - -@BindingAdapter("toolbarColors") -fun toolbarColors(view: Toolbar, swatchInfo: SwatchInfo?) { - swatchInfo?.titleColor?.let { - view.setTitleTextColor(it) - view.navigationIcon?.changeDrawableColor(it) - view.setSubtitleTextColor(it) - view.overflowIcon?.changeDrawableColor(it) - } - swatchInfo?.rgb?.let { view.setBackgroundColor(it) } -} - -@BindingAdapter("collapsingToolbarColors") -fun collapsingToolbarColors(view: CollapsingToolbarLayout, swatchInfo: SwatchInfo?) { - swatchInfo?.titleColor?.let { view.setCollapsedTitleTextColor(it) } - swatchInfo?.rgb?.let { - view.setBackgroundColor(it) - //view.setExpandedTitleColor(it) - } -} - -@BindingAdapter("titleColor") -fun titleColor(view: TextView, swatchInfo: SwatchInfo?) { - swatchInfo?.bodyColor?.let { view.setTextColor(it) } -} - -@BindingAdapter("bodyColor") -fun bodyColor(view: TextView, swatchInfo: SwatchInfo?) { - swatchInfo?.bodyColor?.let { view.setTextColor(it) } -} - -@BindingAdapter("linkColor") -fun linkColor(view: TextView, swatchInfo: SwatchInfo?) { - swatchInfo?.bodyColor?.let { view.setLinkTextColor(it) } -} - -@BindingAdapter("optionTint") -fun optionTint(view: MaterialButton, swatchInfo: SwatchInfo?) { - swatchInfo?.rgb?.let { view.strokeColor = ColorStateList.valueOf(it) } -} - -@BindingAdapter("checkedButtonTint") -fun buttonTint(view: CheckBox, swatchInfo: SwatchInfo?) { - swatchInfo?.bodyColor?.let { view.buttonTintList = ColorStateList.valueOf(it) } - swatchInfo?.bodyColor?.let { view.setTextColor(it) } -} - -@BindingAdapter("startButtonColor") -fun startButtonColor(view: MaterialButton, swatchInfo: SwatchInfo?) { - swatchInfo?.bodyColor?.let { view.iconTint = ColorStateList.valueOf(it) } - swatchInfo?.bodyColor?.let { view.setTextColor(it) } - swatchInfo?.bodyColor?.let { view.strokeColor = ColorStateList.valueOf(it) } -} - -@BindingAdapter("uploadedText") -fun uploadedText(view: TextView, chapterModel: ChapterModel) { - /*if ( - chapterModel.uploadedTime != null && - chapterModel.uploadedTime?.isDateBetween(System.currentTimeMillis() - 8.days.inMilliseconds.toLong(), System.currentTimeMillis()) == true - ) { - view.setTimeAgo(chapterModel.uploadedTime!!, showSeconds = true, autoUpdate = false) - } else { - - }*/ - view.text = chapterModel.uploaded -} - @DslMarker annotation class GlideMarker diff --git a/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt b/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt index 1c0b3b492..d6eb13c19 100644 --- a/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt +++ b/UIViews/src/main/java/com/programmersbox/uiviews/utils/ComposableUtils.kt @@ -56,6 +56,7 @@ import com.skydoves.landscapist.glide.GlideImage import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -960,4 +961,28 @@ fun rememberMutableStateListOf(vararg elements: T): SnapshotStateList< save = { it.toList() }, restore = { it.toMutableStateList() } ) -) { elements.toList().toMutableStateList() } \ No newline at end of file +) { elements.toList().toMutableStateList() } + +@Composable +fun InfiniteListHandler( + listState: LazyListState, + buffer: Int = 2, + onLoadMore: () -> Unit +) { + val loadMore = remember { + derivedStateOf { + val layoutInfo = listState.layoutInfo + val totalItemsNumber = layoutInfo.totalItemsCount + val lastVisibleItemIndex = (layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0) + 1 + + lastVisibleItemIndex > (totalItemsNumber - buffer) + } + } + + LaunchedEffect(loadMore) { + snapshotFlow { loadMore.value } + .drop(1) + .distinctUntilChanged() + .collect { onLoadMore() } + } +} \ No newline at end of file diff --git a/UIViews/src/main/res/layout/fragment_all.xml b/UIViews/src/main/res/layout/fragment_all.xml deleted file mode 100644 index dcc3c864d..000000000 --- a/UIViews/src/main/res/layout/fragment_all.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/UIViews/src/main/res/layout/fragment_recent.xml b/UIViews/src/main/res/layout/fragment_recent.xml deleted file mode 100644 index b1516d0f0..000000000 --- a/UIViews/src/main/res/layout/fragment_recent.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/UIViews/src/main/res/navigation/all_nav.xml b/UIViews/src/main/res/navigation/all_nav.xml index 613bb1c64..c60102067 100644 --- a/UIViews/src/main/res/navigation/all_nav.xml +++ b/UIViews/src/main/res/navigation/all_nav.xml @@ -1,15 +1,13 @@ + android:label="fragment_all"> @@ -10,8 +9,7 @@ + android:label="fragment_recent"> (context, baseListFragment) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnimeHolder = - AnimeHolder(AnimeListItemBinding.inflate(context.layoutInflater, parent, false)) - - override fun AnimeHolder.onBind(item: ItemModel, position: Int) { - bind(item, currentList) - itemView.setOnClickListener { onClick(it, item) } - } -} - -class AnimeHolder(private val binding: AnimeListItemBinding) : RecyclerView.ViewHolder(binding.root) { - - fun bind(info: ItemModel, list: List) { - binding.show = info - binding.root.toolTipText(info.title) - binding.favoriteHeart.changeTint(binding.animeTitle.currentTextColor) - binding.favoriteHeart.check(false) - binding.favoriteHeart.check(list.fastAny { it.url == info.url }) - binding.executePendingBindings() - } - -} \ No newline at end of file diff --git a/animeworld/src/main/java/com/programmersbox/animeworld/CustomFetchNotificationManager.kt b/animeworld/src/main/java/com/programmersbox/animeworld/CustomFetchNotificationManager.kt index 99e440922..84314533b 100644 --- a/animeworld/src/main/java/com/programmersbox/animeworld/CustomFetchNotificationManager.kt +++ b/animeworld/src/main/java/com/programmersbox/animeworld/CustomFetchNotificationManager.kt @@ -74,10 +74,12 @@ class CustomFetchNotificationManager(context: Context) : FetchNotificationManage } else { null } - if (channel == null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - val channelName = context.getString(R.string.fetch_notification_default_channel_name) - channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT) - notificationManager.createNotificationChannel(channel) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + if (channel == null) { + val channelName = context.getString(R.string.fetch_notification_default_channel_name) + channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT) + notificationManager.createNotificationChannel(channel) + } } } diff --git a/animeworld/src/main/java/com/programmersbox/animeworld/GenericAnime.kt b/animeworld/src/main/java/com/programmersbox/animeworld/GenericAnime.kt index 9fd8a1822..77625b596 100644 --- a/animeworld/src/main/java/com/programmersbox/animeworld/GenericAnime.kt +++ b/animeworld/src/main/java/com/programmersbox/animeworld/GenericAnime.kt @@ -5,27 +5,27 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.widget.Toast +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material.Card -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Icon -import androidx.compose.material.Text +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items +import androidx.compose.material.* import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastAny import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import androidx.mediarouter.app.MediaRouteDialogFactory import androidx.preference.Preference import androidx.preference.SwitchPreference -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.google.accompanist.placeholder.PlaceholderHighlight import com.google.accompanist.placeholder.material.placeholder import com.google.accompanist.placeholder.material.shimmer @@ -39,18 +39,18 @@ import com.programmersbox.anime_sources.anime.WcoStream import com.programmersbox.anime_sources.anime.Yts import com.programmersbox.animeworld.cast.ExpandedControlsActivity import com.programmersbox.animeworld.ytsdatabase.Torrent +import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.gsonutils.fromJson import com.programmersbox.helpfulutils.requestPermissions import com.programmersbox.helpfulutils.runOnUIThread import com.programmersbox.helpfulutils.sharedPrefNotNullDelegate import com.programmersbox.models.ApiService import com.programmersbox.models.ChapterModel +import com.programmersbox.models.ItemModel import com.programmersbox.models.sourcePublish import com.programmersbox.sharedutils.AppUpdate import com.programmersbox.sharedutils.MainLogo -import com.programmersbox.uiviews.BaseListFragment import com.programmersbox.uiviews.GenericInfo -import com.programmersbox.uiviews.ItemListAdapter import com.programmersbox.uiviews.SettingsDsl import com.programmersbox.uiviews.utils.NotificationLogo import com.tonyodev.fetch2.* @@ -75,11 +75,6 @@ class GenericAnime(val context: Context) : GenericInfo { override val apkString: AppUpdate.AppUpdates.() -> String? get() = { anime_file } - override fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter = - (AnimeAdapter(context, baseListFragment) as ItemListAdapter) - - override fun createLayoutManager(context: Context): RecyclerView.LayoutManager = LinearLayoutManager(context) - override fun downloadChapter(chapterModel: ChapterModel, title: String) { if ((chapterModel.source as? ShowApi)?.canStream == false) { Toast.makeText(context, context.getString(R.string.source_no_stream, chapterModel.source.serviceName), Toast.LENGTH_SHORT).show() @@ -329,7 +324,6 @@ class GenericAnime(val context: Context) : GenericInfo { .fillMaxWidth() .padding(5.dp) ) { - Row( modifier = Modifier .fillMaxWidth() @@ -354,4 +348,40 @@ class GenericAnime(val context: Context) : GenericInfo { } } + @ExperimentalMaterialApi + @ExperimentalFoundationApi + @Composable + override fun ItemListView( + list: List, + favorites: List, + listState: LazyListState, + onClick: (ItemModel) -> Unit + ) { + LazyColumn(state = listState) { + items(list) { + Card( + onClick = { onClick(it) }, + modifier = Modifier + .fillMaxWidth() + .padding(5.dp), + elevation = 5.dp + ) { + ListItem( + icon = { + Icon( + if (favorites.fastAny { f -> f.url == it.url }) Icons.Default.Favorite else Icons.Default.FavoriteBorder, + contentDescription = null, + ) + }, + text = { Text(it.title) }, + overlineText = { Text(it.source.serviceName) }, + secondaryText = if (it.description.isNotEmpty()) { + { Text(it.description) } + } else null + ) + } + } + } + } + } diff --git a/animeworld/src/main/res/layout/anime_list_item.xml b/animeworld/src/main/res/layout/anime_list_item.xml deleted file mode 100644 index 855405a87..000000000 --- a/animeworld/src/main/res/layout/anime_list_item.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt index 6099f7266..bff70f6c3 100644 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt +++ b/mangaworld/src/main/java/com/programmersbox/mangaworld/GenericManga.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.os.Environment import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.GridCells +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyVerticalGrid import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.runtime.Composable @@ -15,22 +16,16 @@ import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.preference.Preference import androidx.preference.SwitchPreference -import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView +import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.gsonutils.toJson import com.programmersbox.helpfulutils.downloadManager import com.programmersbox.helpfulutils.requestPermissions import com.programmersbox.manga_sources.Sources import com.programmersbox.manga_sources.utilities.NetworkHelper -import com.programmersbox.models.ApiService -import com.programmersbox.models.ChapterModel -import com.programmersbox.models.Storage -import com.programmersbox.models.sourcePublish +import com.programmersbox.models.* import com.programmersbox.sharedutils.AppUpdate import com.programmersbox.sharedutils.MainLogo -import com.programmersbox.uiviews.BaseListFragment import com.programmersbox.uiviews.GenericInfo -import com.programmersbox.uiviews.ItemListAdapter import com.programmersbox.uiviews.SettingsDsl import com.programmersbox.uiviews.utils.* import io.reactivex.disposables.CompositeDisposable @@ -53,12 +48,6 @@ class GenericManga(val context: Context) : GenericInfo { override val apkString: AppUpdate.AppUpdates.() -> String? get() = { manga_file } - override fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter = - (MangaGalleryAdapter(context, baseListFragment) as ItemListAdapter) - - override fun createLayoutManager(context: Context): RecyclerView.LayoutManager = - AutoFitGridLayoutManager(context, 360).apply { orientation = GridLayoutManager.VERTICAL } - override fun chapterOnClick(model: ChapterModel, allChapters: List, context: Context) { context.startActivity( Intent(context, ReadActivity::class.java).apply { @@ -157,4 +146,25 @@ class GenericManga(val context: Context) : GenericInfo { } } + @ExperimentalMaterialApi + @ExperimentalFoundationApi + @Composable + override fun ItemListView( + list: List, + favorites: List, + listState: LazyListState, + onClick: (ItemModel) -> Unit + ) { + LazyVerticalGrid( + cells = GridCells.Adaptive(ComposableUtils.IMAGE_WIDTH), + state = listState + ) { + items(list.size) { i -> + list.getOrNull(i)?.let { + CoverCard(imageUrl = it.imageUrl, name = it.title, placeHolder = R.drawable.manga_world_round_logo) { onClick(it) } + } + } + } + } + } \ No newline at end of file diff --git a/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaGalleryAdapter.kt b/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaGalleryAdapter.kt deleted file mode 100644 index a5fcb7433..000000000 --- a/mangaworld/src/main/java/com/programmersbox/mangaworld/MangaGalleryAdapter.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.programmersbox.mangaworld - -import android.content.Context -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.Glide -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.programmersbox.favoritesdatabase.DbModel -import com.programmersbox.helpfulutils.layoutInflater -import com.programmersbox.mangaworld.databinding.MangaGalleryItemBinding -import com.programmersbox.models.ItemModel -import com.programmersbox.uiviews.BaseListFragment -import com.programmersbox.uiviews.ItemListAdapter -import com.programmersbox.uiviews.utils.toolTipText - -class MangaGalleryAdapter(context: Context, baseListFragment: BaseListFragment) : - ItemListAdapter(context, baseListFragment) { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryHolder = - GalleryHolder(MangaGalleryItemBinding.inflate(context.layoutInflater, parent, false)) - - override fun GalleryHolder.onBind(item: ItemModel, position: Int) { - bind(item) - itemView.setOnClickListener { onClick(it, item) } - Glide.with(context) - .asBitmap() - .load(item.imageUrl) - //.override(360, 480) - .fitCenter() - .transform(RoundedCorners(15)) - .fallback(R.drawable.manga_world_round_logo) - .placeholder(R.drawable.manga_world_round_logo) - .error(R.drawable.manga_world_round_logo) - .into(cover) - /*.into { - resourceReady { image, _ -> - //cover.setImageBitmap(image.glowEffect(10, title.currentTextColor) ?: image) - cover.setImageBitmap(image) - *//*if (context.usePalette) { - swatch = image.getPalette().vibrantSwatch - }*//* - } - }*/ - } - - override val currentList: MutableList get() = mutableListOf() - override val previousList: MutableList get() = mutableListOf() - override fun update(list: List, check: (ItemModel, DbModel) -> Boolean) {} -} - -class GalleryHolder(private val binding: MangaGalleryItemBinding) : RecyclerView.ViewHolder(binding.root) { - val cover = binding.galleryListCover - val title = binding.galleryListTitle - val layout = binding.galleryListLayout - - fun bind(item: ItemModel) { - binding.model = item - binding.root.toolTipText(item.title) - binding.executePendingBindings() - } -} \ No newline at end of file diff --git a/mangaworld/src/main/res/layout/manga_gallery_item.xml b/mangaworld/src/main/res/layout/manga_gallery_item.xml deleted file mode 100644 index 11eadec48..000000000 --- a/mangaworld/src/main/res/layout/manga_gallery_item.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/novelworld/build.gradle b/novelworld/build.gradle index 88752c604..b80d74ccd 100644 --- a/novelworld/build.gradle +++ b/novelworld/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' + id 'androidx.navigation.safeargs.kotlin' id 'com.google.gms.google-services' id 'com.google.firebase.crashlytics' id 'com.mikepenz.aboutlibraries.plugin' diff --git a/novelworld/src/main/java/com/programmersbox/novelworld/GenericNovel.kt b/novelworld/src/main/java/com/programmersbox/novelworld/GenericNovel.kt index 13cbe2764..f005cb5e4 100644 --- a/novelworld/src/main/java/com/programmersbox/novelworld/GenericNovel.kt +++ b/novelworld/src/main/java/com/programmersbox/novelworld/GenericNovel.kt @@ -2,27 +2,29 @@ package com.programmersbox.novelworld import android.content.Context import android.content.Intent +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.items import androidx.compose.material.Card import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.ListItem import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.google.accompanist.placeholder.material.placeholder +import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.gsonutils.toJson import com.programmersbox.models.ApiService import com.programmersbox.models.ChapterModel +import com.programmersbox.models.ItemModel import com.programmersbox.novel_sources.Sources import com.programmersbox.sharedutils.AppUpdate import com.programmersbox.sharedutils.MainLogo -import com.programmersbox.uiviews.BaseListFragment import com.programmersbox.uiviews.GenericInfo -import com.programmersbox.uiviews.ItemListAdapter import com.programmersbox.uiviews.utils.ChapterModelSerializer import com.programmersbox.uiviews.utils.NotificationLogo import org.koin.dsl.module @@ -34,10 +36,6 @@ val appModule = module { } class GenericNovel(val context: Context) : GenericInfo { - override fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter = - (NovelAdapter(context, baseListFragment) as ItemListAdapter) - - override fun createLayoutManager(context: Context): RecyclerView.LayoutManager = LinearLayoutManager(context) override fun chapterOnClick(model: ChapterModel, allChapters: List, context: Context) { context.startActivity( @@ -83,4 +81,18 @@ class GenericNovel(val context: Context) : GenericInfo { } } + @ExperimentalMaterialApi + @ExperimentalFoundationApi + @Composable + override fun ItemListView( + list: List, + favorites: List, + listState: LazyListState, + onClick: (ItemModel) -> Unit + ) { + LazyColumn(state = listState) { + items(list) { ListItem(text = { Text(it.title) }) } + } + } + } \ No newline at end of file diff --git a/novelworld/src/main/java/com/programmersbox/novelworld/NovelAdapter.kt b/novelworld/src/main/java/com/programmersbox/novelworld/NovelAdapter.kt deleted file mode 100644 index 7c811b368..000000000 --- a/novelworld/src/main/java/com/programmersbox/novelworld/NovelAdapter.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.programmersbox.novelworld - -import android.content.Context -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import com.programmersbox.favoritesdatabase.DbModel -import com.programmersbox.helpfulutils.layoutInflater -import com.programmersbox.models.ItemModel -import com.programmersbox.novelworld.databinding.NovelListItemBinding -import com.programmersbox.uiviews.BaseListFragment -import com.programmersbox.uiviews.ItemListAdapter -import com.programmersbox.uiviews.utils.toolTipText - -class NovelAdapter( - context: Context, - baseListFragment: BaseListFragment, -) : ItemListAdapter(context, baseListFragment) { - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NovelHolder = - NovelHolder(NovelListItemBinding.inflate(context.layoutInflater, parent, false)) - - override fun NovelHolder.onBind(item: ItemModel, position: Int) { - bind(item, currentList) - itemView.setOnClickListener { onClick(it, item) } - } -} - -class NovelHolder(private val binding: NovelListItemBinding) : RecyclerView.ViewHolder(binding.root) { - - fun bind(info: ItemModel, list: List) { - binding.show = info - binding.root.toolTipText(info.title) - /*binding.favoriteHeart.changeTint(binding.animeTitle.currentTextColor) - binding.favoriteHeart.check(false) - binding.favoriteHeart.check(list.any { it.url == info.url })*/ - binding.executePendingBindings() - } - -} \ No newline at end of file diff --git a/novelworld/src/main/res/layout/novel_list_item.xml b/novelworld/src/main/res/layout/novel_list_item.xml deleted file mode 100644 index fca076ff4..000000000 --- a/novelworld/src/main/res/layout/novel_list_item.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/otakumanager/src/main/java/com/programmersbox/otakumanager/OtakuManagerApp.kt b/otakumanager/src/main/java/com/programmersbox/otakumanager/OtakuManagerApp.kt index 833077625..aa5559ad2 100644 --- a/otakumanager/src/main/java/com/programmersbox/otakumanager/OtakuManagerApp.kt +++ b/otakumanager/src/main/java/com/programmersbox/otakumanager/OtakuManagerApp.kt @@ -1,17 +1,17 @@ package com.programmersbox.otakumanager import android.content.Context +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable -import androidx.recyclerview.widget.RecyclerView +import com.programmersbox.favoritesdatabase.DbModel import com.programmersbox.manga_sources.utilities.NetworkHelper import com.programmersbox.models.ApiService import com.programmersbox.models.ChapterModel +import com.programmersbox.models.ItemModel import com.programmersbox.sharedutils.AppUpdate import com.programmersbox.sharedutils.FirebaseDb import com.programmersbox.sharedutils.MainLogo -import com.programmersbox.uiviews.BaseListFragment import com.programmersbox.uiviews.GenericInfo -import com.programmersbox.uiviews.ItemListAdapter import com.programmersbox.uiviews.OtakuApp import com.programmersbox.uiviews.utils.NotificationLogo import com.programmersbox.uiviews.utils.shouldCheck @@ -43,14 +43,6 @@ val appModule = module { object : GenericInfo { override val apkString: AppUpdate.AppUpdates.() -> String? get() = { otakumanager_file } - override fun createAdapter(context: Context, baseListFragment: BaseListFragment): ItemListAdapter { - throw Exception("This should not be seen") - } - - override fun createLayoutManager(context: Context): RecyclerView.LayoutManager { - throw Exception("This should not be seen") - } - override fun chapterOnClick(model: ChapterModel, allChapters: List, context: Context) { throw Exception("This should not be seen") } @@ -77,6 +69,15 @@ val appModule = module { } + @Composable + override fun ItemListView( + list: List, + favorites: List, + listState: LazyListState, + onClick: (ItemModel) -> Unit + ) { + } + } } } \ No newline at end of file diff --git a/sharedutils/build.gradle b/sharedutils/build.gradle index 6c94cd311..d385c4ec9 100644 --- a/sharedutils/build.gradle +++ b/sharedutils/build.gradle @@ -47,7 +47,7 @@ dependencies { implementation 'com.google.android.gms:play-services-auth:19.2.0' implementation 'com.firebaseui:firebase-ui-auth:8.0.0' implementation 'com.google.firebase:firebase-firestore-ktx:23.0.3' - implementation 'com.google.firebase:firebase-database-ktx:20.0.1' + implementation 'com.google.firebase:firebase-database-ktx:20.0.2' implementation coroutinesCore implementation coroutinesAndroid