package com.huanchengfly.tieba.post import android.annotation.SuppressLint import android.app.job.JobInfo import android.app.job.JobScheduler import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.Spring import androidx.compose.animation.core.VisibilityThreshold import androidx.compose.animation.core.spring import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Alignment import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.navigation.plusAssign import androidx.window.layout.FoldingFeature import androidx.window.layout.WindowInfoTracker import com.google.accompanist.navigation.animation.rememberAnimatedNavController import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi import com.google.accompanist.navigation.material.ModalBottomSheetLayout import com.google.accompanist.navigation.material.rememberBottomSheetNavigator import com.google.accompanist.systemuicontroller.SystemUiController import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage import com.huanchengfly.tieba.post.arch.BaseComposeActivity import com.huanchengfly.tieba.post.services.NotifyJobService import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme import com.huanchengfly.tieba.post.ui.page.NavGraphs import com.huanchengfly.tieba.post.ui.page.destinations.MainPageDestination import com.huanchengfly.tieba.post.ui.utils.DevicePosture import com.huanchengfly.tieba.post.ui.utils.isBookPosture import com.huanchengfly.tieba.post.ui.utils.isSeparating import com.huanchengfly.tieba.post.utils.AccountUtil import com.huanchengfly.tieba.post.utils.JobServiceUtil import com.huanchengfly.tieba.post.utils.PermissionUtils import com.huanchengfly.tieba.post.utils.TiebaUtil import com.huanchengfly.tieba.post.utils.newIntentFilter import com.huanchengfly.tieba.post.utils.requestPermission import com.ramcosta.composedestinations.DestinationsNavHost import com.ramcosta.composedestinations.animations.defaults.RootNavGraphDefaultAnimations import com.ramcosta.composedestinations.animations.rememberAnimatedNavHostEngine import com.ramcosta.composedestinations.navigation.dependency import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch val LocalNotificationCountFlow = staticCompositionLocalOf> { throw IllegalStateException("not allowed here!") } @AndroidEntryPoint class MainActivityV2 : BaseComposeActivity() { private val handler = Handler(Looper.getMainLooper()) private val newMessageReceiver: BroadcastReceiver = NewMessageReceiver() private val notificationCountFlow: MutableSharedFlow = MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) private val devicePostureFlow: StateFlow by lazy { WindowInfoTracker.getOrCreate(this) .windowLayoutInfo(this) .flowWithLifecycle(lifecycle) .map { layoutInfo -> val foldingFeature = layoutInfo.displayFeatures .filterIsInstance() .firstOrNull() when { isBookPosture(foldingFeature) -> DevicePosture.BookPosture(foldingFeature.bounds) isSeparating(foldingFeature) -> DevicePosture.Separating(foldingFeature.bounds, foldingFeature.orientation) else -> DevicePosture.NormalPosture } } .stateIn( scope = lifecycleScope, started = SharingStarted.Eagerly, initialValue = DevicePosture.NormalPosture ) } private fun fetchAccount() { lifecycleScope.launch(Dispatchers.Main) { if (AccountUtil.isLoggedIn()) { AccountUtil.fetchAccountFlow() .flowOn(Dispatchers.IO) .catch { e -> toastShort(e.getErrorMessage()) e.printStackTrace() } .collect() } } } private fun requestNotificationPermission() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && AccountUtil.isLoggedIn()) { requestPermission { permissions = listOf(PermissionUtils.POST_NOTIFICATIONS) description = getString(R.string.desc_permission_post_notifications) } } } private fun initAutoSign() { runCatching { TiebaUtil.initAutoSign(this) } } override fun onStart() { super.onStart() runCatching { ContextCompat.registerReceiver( this, newMessageReceiver, newIntentFilter(NotifyJobService.ACTION_NEW_MESSAGE), ContextCompat.RECEIVER_NOT_EXPORTED ) startService(Intent(this, NotifyJobService::class.java)) val builder = JobInfo.Builder( JobServiceUtil.getJobId(this), ComponentName(this, NotifyJobService::class.java) ) .setPersisted(true) .setPeriodic(30 * 60 * 1000L) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(builder.build()) } handler.postDelayed({ requestNotificationPermission() }, 100) } override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() super.onCreate(savedInstanceState) } override fun onCreateContent(systemUiController: SystemUiController) { super.onCreateContent(systemUiController) fetchAccount() initAutoSign() } @OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialNavigationApi::class) @Composable override fun createContent() { CompositionLocalProvider(LocalNotificationCountFlow provides notificationCountFlow) { Surface( color = ExtendedTheme.colors.background ) { val animationSpec = spring( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ) val engine = rememberAnimatedNavHostEngine( navHostContentAlignment = Alignment.TopStart, rootDefaultAnimations = RootNavGraphDefaultAnimations( enterTransition = { slideIntoContainer( AnimatedContentScope.SlideDirection.Start, animationSpec = animationSpec, initialOffset = { it } ) }, exitTransition = { slideOutOfContainer( AnimatedContentScope.SlideDirection.End, animationSpec = animationSpec, targetOffset = { -it } ) }, popEnterTransition = { slideIntoContainer( AnimatedContentScope.SlideDirection.Start, animationSpec = animationSpec, initialOffset = { -it } ) }, popExitTransition = { slideOutOfContainer( AnimatedContentScope.SlideDirection.End, animationSpec = animationSpec, targetOffset = { it } ) }, ), ) val navController = rememberAnimatedNavController() val bottomSheetNavigator = rememberBottomSheetNavigator(animationSpec = spring(stiffness = Spring.StiffnessMediumLow)) navController.navigatorProvider += bottomSheetNavigator ModalBottomSheetLayout( bottomSheetNavigator = bottomSheetNavigator, sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) ) { DestinationsNavHost( navController = navController, navGraph = NavGraphs.root, engine = engine, dependenciesContainerBuilder = { dependency(MainPageDestination) { this@MainActivityV2 } dependency(devicePostureFlow) } ) } } } } private inner class NewMessageReceiver : BroadcastReceiver() { @SuppressLint("RestrictedApi") override fun onReceive(context: Context, intent: Intent) { if (intent.action == NotifyJobService.ACTION_NEW_MESSAGE) { val channel = intent.getStringExtra("channel") val count = intent.getIntExtra("count", 0) if (channel != null && channel == NotifyJobService.CHANNEL_TOTAL) { lifecycleScope.launch { notificationCountFlow.emit(count) } } } } } }