Skip to content

Commit 9392d0d

Browse files
committed
- brought Otaku Manager up to a good enough place to start actually using it.
1 parent ec4fad9 commit 9392d0d

File tree

12 files changed

+729
-60
lines changed

12 files changed

+729
-60
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ local.properties
1717
/mangaworld/google-services.json
1818
/novelworld/google-services.json
1919
/animeworldtv/google-services.json
20+
/otakumanager/google-services.json

.idea/misc.xml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

otakumanager/build.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,26 @@ android {
5353
}
5454

5555
dependencies {
56+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
5657
implementation androidCore
5758
implementation appCompat
5859
implementation material
5960
implementation compose.compose
61+
implementation "androidx.navigation:navigation-compose:2.4.0-alpha05"
62+
implementation "com.google.accompanist:accompanist-systemuicontroller:0.15.0"
63+
implementation 'com.github.alorma:compose-settings:0.2.0'
6064
implementation jakepurple13Tools.helpfultools
6165
implementation firebaseCrash.crash
6266
implementation koin.koin
67+
implementation 'androidx.palette:palette-ktx:1.0.0'
6368
implementation 'com.google.firebase:firebase-auth:21.0.1'
6469
implementation 'com.google.android.gms:play-services-auth:19.2.0'
6570
implementation 'com.google.firebase:firebase-firestore-ktx:23.0.3'
6671
implementation 'com.google.firebase:firebase-database-ktx:20.0.1'
6772
implementation "androidx.compose.ui:ui-tooling-preview:$jetpack"
6873
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
74+
implementation "androidx.datastore:datastore:1.0.0-rc02"
75+
implementation "androidx.datastore:datastore-preferences:1.0.0-rc02"
6976
testImplementation 'junit:junit:4.+'
7077
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
7178
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

otakumanager/src/main/AndroidManifest.xml

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
package="com.programmersbox.otakumanager">
44

5+
<uses-permission android:name="android.permission.INTERNET" />
6+
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
7+
58
<application
69
android:allowBackup="true"
710
android:icon="@mipmap/ic_launcher"

otakumanager/src/main/java/com/programmersbox/otakumanager/CustomFirebaseUtils.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import com.programmersbox.sharedutils.FirebaseAuthentication
1515
import io.reactivex.Completable
1616
import io.reactivex.subjects.PublishSubject
1717

18-
class FirebaseDb2(
18+
data class FirebaseDb2(
1919
val DOCUMENT_ID: String,
2020
val CHAPTERS_ID: String,
2121
val COLLECTION_ID: String,

otakumanager/src/main/java/com/programmersbox/otakumanager/MainActivity.kt

+139-26
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package com.programmersbox.otakumanager
33
import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
6+
import androidx.annotation.StringRes
7+
import androidx.compose.animation.ExperimentalAnimationApi
68
import androidx.compose.animation.animateColorAsState
79
import androidx.compose.foundation.ExperimentalFoundationApi
810
import androidx.compose.foundation.combinedClickable
@@ -14,31 +16,48 @@ import androidx.compose.foundation.lazy.GridCells
1416
import androidx.compose.foundation.lazy.LazyRow
1517
import androidx.compose.foundation.lazy.LazyVerticalGrid
1618
import androidx.compose.foundation.lazy.items
17-
import androidx.compose.foundation.shape.RoundedCornerShape
1819
import androidx.compose.foundation.text.KeyboardActions
1920
import androidx.compose.foundation.text.KeyboardOptions
2021
import androidx.compose.material.*
2122
import androidx.compose.material.icons.Icons
23+
import androidx.compose.material.icons.filled.Book
2224
import androidx.compose.material.icons.filled.Cancel
25+
import androidx.compose.material.icons.filled.Favorite
26+
import androidx.compose.material.icons.filled.Menu
2327
import androidx.compose.runtime.*
2428
import androidx.compose.runtime.rxjava2.subscribeAsState
2529
import androidx.compose.ui.Modifier
30+
import androidx.compose.ui.graphics.vector.ImageVector
2631
import androidx.compose.ui.platform.LocalFocusManager
32+
import androidx.compose.ui.res.stringResource
2733
import androidx.compose.ui.text.input.ImeAction
2834
import androidx.compose.ui.tooling.preview.Preview
2935
import androidx.compose.ui.unit.dp
36+
import androidx.navigation.NavController
37+
import androidx.navigation.NavDestination.Companion.hierarchy
38+
import androidx.navigation.NavGraph.Companion.findStartDestination
39+
import androidx.navigation.NavType
40+
import androidx.navigation.compose.*
41+
import com.google.accompanist.systemuicontroller.rememberSystemUiController
3042
import com.google.android.material.composethemeadapter.MdcTheme
3143
import com.google.android.material.dialog.MaterialAlertDialogBuilder
3244
import com.programmersbox.favoritesdatabase.DbModel
45+
import com.programmersbox.favoritesdatabase.toItemModel
46+
import com.programmersbox.gsonutils.fromJson
47+
import com.programmersbox.gsonutils.toJson
3348
import com.programmersbox.models.ApiService
3449
import com.programmersbox.otakumanager.ui.theme.OtakuWorldTheme
35-
import com.programmersbox.sharedutils.FirebaseAuthentication
50+
import com.programmersbox.sharedutils.AppUpdate
51+
import com.programmersbox.sharedutils.appUpdateCheck
3652
import com.programmersbox.uiviews.GenericInfo
3753
import com.programmersbox.uiviews.utils.ComposableUtils
3854
import com.programmersbox.uiviews.utils.CoverCard
3955
import com.programmersbox.uiviews.utils.CustomChip
4056
import io.reactivex.Flowable
57+
import io.reactivex.Single
4158
import io.reactivex.android.schedulers.AndroidSchedulers
59+
import io.reactivex.disposables.CompositeDisposable
60+
import io.reactivex.rxkotlin.addTo
4261
import io.reactivex.schedulers.Schedulers
4362
import org.koin.android.ext.android.inject
4463
import com.programmersbox.anime_sources.Sources as ASources
@@ -47,9 +66,13 @@ import com.programmersbox.novel_sources.Sources as NSources
4766

4867
class MainActivity : ComponentActivity() {
4968

50-
private val animeListener = FirebaseDb2("favoriteShows", "episodesWatched", "animeworld", "showUrl", "numEpisodes").FirebaseListener()
51-
private val mangaListener = FirebaseDb2("favoriteManga", "chaptersRead", "mangaworld", "mangaUrl", "chapterCount").FirebaseListener()
52-
private val novelListener = FirebaseDb2("favoriteNovels", "novelsChaptersRead", "novelworld", "novelUrl", "novelNumChapters").FirebaseListener()
69+
private val animeFire = FirebaseDb2("favoriteShows", "episodesWatched", "animeworld", "showUrl", "numEpisodes")
70+
private val mangaFire = FirebaseDb2("favoriteManga", "chaptersRead", "mangaworld", "mangaUrl", "chapterCount")
71+
private val novelFire = FirebaseDb2("favoriteNovels", "novelsChaptersRead", "novelworld", "novelUrl", "novelNumChapters")
72+
73+
private val animeListener = animeFire.FirebaseListener()
74+
private val mangaListener = mangaFire.FirebaseListener()
75+
private val novelListener = novelFire.FirebaseListener()
5376

5477
private val genericInfo by inject<GenericInfo>()
5578

@@ -63,16 +86,116 @@ class MainActivity : ComponentActivity() {
6386
private val mangaSources = MSources.values().map { it.serviceName }
6487
private val novelSources = NSources.values().map { it.serviceName }
6588

89+
sealed class Screen(val route: String, @StringRes val resourceId: Int, val icon: ImageVector) {
90+
object Favorites : Screen("mainUi", R.string.viewFavoritesMenu, Icons.Filled.Favorite)
91+
object Settings : Screen("settings", R.string.settings, Icons.Filled.Menu)
92+
object Details : Screen("details?info={item}", R.string.app_name, Icons.Filled.Book)
93+
}
94+
95+
private val disposable = CompositeDisposable()
96+
97+
@ExperimentalAnimationApi
6698
@ExperimentalMaterialApi
6799
@ExperimentalFoundationApi
68100
override fun onCreate(savedInstanceState: Bundle?) {
69101
super.onCreate(savedInstanceState)
70102

103+
Single.create<AppUpdate.AppUpdates> {
104+
AppUpdate.getUpdate()?.let { d -> it.onSuccess(d) } ?: it.onError(Exception("Something went wrong"))
105+
}
106+
.subscribeOn(Schedulers.io())
107+
.observeOn(AndroidSchedulers.mainThread())
108+
.doOnError { }
109+
.subscribe(appUpdateCheck::onNext)
110+
.addTo(disposable)
111+
112+
val screenList = listOf(
113+
Screen.Favorites,
114+
Screen.Settings
115+
)
116+
71117
setContent {
72118
MdcTheme {
73119
// A surface container using the 'background' color from the theme
74120
Surface(color = MaterialTheme.colors.background) {
75-
MainUi()
121+
122+
val navController = rememberNavController()
123+
124+
Scaffold(
125+
bottomBar = {
126+
BottomNavigation {
127+
val navBackStackEntry by navController.currentBackStackEntryAsState()
128+
val currentDestination = navBackStackEntry?.destination
129+
screenList.forEach { screen ->
130+
BottomNavigationItem(
131+
icon = { Icon(screen.icon, contentDescription = null) },
132+
label = { Text(stringResource(id = screen.resourceId)) },
133+
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
134+
onClick = {
135+
navController.navigate(screen.route) {
136+
// Pop up to the start destination of the graph to
137+
// avoid building up a large stack of destinations
138+
// on the back stack as users select items
139+
popUpTo(navController.graph.findStartDestination().id) {
140+
saveState = true
141+
}
142+
// Avoid multiple copies of the same destination when
143+
// reselecting the same item
144+
launchSingleTop = true
145+
// Restore state when reselecting a previously selected item
146+
restoreState = true
147+
}
148+
}
149+
)
150+
}
151+
}
152+
}
153+
) { p ->
154+
155+
NavHost(navController = navController, startDestination = Screen.Favorites.route, modifier = Modifier.padding(p)) {
156+
composable(Screen.Favorites.route) { MainUi(navController) }
157+
composable(Screen.Settings.route) { OtakuSettings(this@MainActivity, genericInfo) }
158+
composable(
159+
Screen.Details.route,
160+
arguments = listOf(
161+
navArgument("item") {
162+
type = NavType.StringType
163+
}
164+
)
165+
) {
166+
val i = it.arguments?.getString("item")?.fromJson<DbModel>()
167+
?.let { genericInfo.toSource(it.source)?.let { it1 -> it.toItemModel(it1) } }
168+
DetailsScreen(
169+
info = i!!,
170+
logoId = when (i.source.serviceName) {
171+
in animeSources -> R.drawable.animeworld_logo
172+
in mangaSources -> R.drawable.mangaworld_logo
173+
in novelSources -> R.drawable.novelworld_logo
174+
else -> R.drawable.ic_launcher_foreground
175+
},
176+
firebase = when (i.source.serviceName) {
177+
in animeSources -> animeFire
178+
in mangaSources -> mangaFire
179+
in novelSources -> novelFire
180+
else -> null
181+
},
182+
itemListener = when (i.source.serviceName) {
183+
in animeSources -> animeFire
184+
in mangaSources -> mangaFire
185+
in novelSources -> novelFire
186+
else -> null
187+
}?.copy()?.FirebaseListener(),
188+
chapterListener = when (i.source.serviceName) {
189+
in animeSources -> animeFire
190+
in mangaSources -> mangaFire
191+
in novelSources -> novelFire
192+
else -> null
193+
}?.copy()?.FirebaseListener(),
194+
navController = navController
195+
)
196+
}
197+
}
198+
}
76199
}
77200
}
78201
}
@@ -81,7 +204,10 @@ class MainActivity : ComponentActivity() {
81204
@ExperimentalMaterialApi
82205
@ExperimentalFoundationApi
83206
@Composable
84-
fun MainUi() {
207+
fun MainUi(navController: NavController) {
208+
209+
val systemUi = rememberSystemUiController()
210+
systemUi.setStatusBarColor(animateColorAsState(MaterialTheme.colors.primaryVariant).value)
85211

86212
val focusManager = LocalFocusManager.current
87213

@@ -174,20 +300,7 @@ class MainActivity : ComponentActivity() {
174300
}
175301
},
176302
bottomBar = {
177-
if (FirebaseAuthentication.currentUser == null) {
178-
Button(
179-
onClick = {
180-
//if (FirebaseAuthentication.currentUser == null) {
181-
//TODO: if not logged in, show empty state for needing to login
182-
FirebaseAuthentication.signIn(this@MainActivity)
183-
//}
184-
},
185-
modifier = Modifier.fillMaxWidth(),
186-
shape = RoundedCornerShape(0f)
187-
) {
188-
Text("Login")
189-
}
190-
}
303+
//TODO: if not logged in, show empty state for needing to login
191304
}
192305
) {
193306

@@ -213,16 +326,15 @@ class MainActivity : ComponentActivity() {
213326
) {
214327

215328
if (info.value.size == 1) {
216-
//TODO: Open a modified details view here
217-
//val item = info.value.firstOrNull()?.let { genericInfo.toSource(it.source)?.let { it1 -> it.toItemModel(it1) } }
218-
//navController.navigate(FavoriteFragmentDirections.actionFavoriteFragmentToDetailsFragment(item))
329+
val item = info.value.firstOrNull()
330+
navController.navigate(MainActivity.Screen.Details.route.replace("{item}", item.toJson()))
219331
} else {
220332
MaterialAlertDialogBuilder(this@MainActivity)
221333
.setTitle(R.string.chooseASource)
222334
.setItems(info.value.map { "${it.source} - ${it.title}" }.toTypedArray()) { d, i ->
223-
//val item = info.value[i].let { genericInfo.toSource(it.source)?.let { it1 -> it.toItemModel(it1) } }
224-
//navController.navigate(FavoriteFragmentDirections.actionFavoriteFragmentToDetailsFragment(item))
335+
val item = info.value[i]
225336
d.dismiss()
337+
navController.navigate(MainActivity.Screen.Details.route.replace("{item}", item.toJson()))
226338
}
227339
.show()
228340
}
@@ -240,6 +352,7 @@ class MainActivity : ComponentActivity() {
240352
animeListener.unregister()
241353
mangaListener.unregister()
242354
novelListener.unregister()
355+
disposable.dispose()
243356
}
244357
}
245358

0 commit comments

Comments
 (0)