mirror of
https://github.com/bitfireAT/davx5-ose
synced 2024-09-06 18:01:11 +00:00
Rewrite intro pages to Compose (dropping all Fragments) (#626)
This commit is contained in:
parent
1cbfedc9e4
commit
42f99e644d
|
@ -27,8 +27,7 @@ import at.bitfire.davdroid.R
|
||||||
import at.bitfire.davdroid.resource.TaskUtils
|
import at.bitfire.davdroid.resource.TaskUtils
|
||||||
import at.bitfire.davdroid.settings.Settings
|
import at.bitfire.davdroid.settings.Settings
|
||||||
import at.bitfire.davdroid.settings.SettingsManager
|
import at.bitfire.davdroid.settings.SettingsManager
|
||||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment
|
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
|
||||||
import at.bitfire.davdroid.ui.intro.OpenSourceFragment
|
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -272,10 +271,10 @@ class AppSettingsActivity: AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetHints() {
|
private fun resetHints() {
|
||||||
settings.remove(BatteryOptimizationsFragment.Model.HINT_BATTERY_OPTIMIZATIONS)
|
settings.remove(BatteryOptimizationsPage.Model.HINT_BATTERY_OPTIMIZATIONS)
|
||||||
settings.remove(BatteryOptimizationsFragment.Model.HINT_AUTOSTART_PERMISSION)
|
settings.remove(BatteryOptimizationsPage.Model.HINT_AUTOSTART_PERMISSION)
|
||||||
settings.remove(TasksModel.HINT_OPENTASKS_NOT_INSTALLED)
|
settings.remove(TasksModel.HINT_OPENTASKS_NOT_INSTALLED)
|
||||||
settings.remove(OpenSourceFragment.Model.SETTING_NEXT_DONATION_POPUP)
|
//settings.remove(OpenSourceFragment.Model.SETTING_NEXT_DONATION_POPUP)
|
||||||
Snackbar.make(requireView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(requireView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,14 @@ package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.view.LayoutInflater
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
@ -40,95 +39,103 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.AndroidViewModel
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import at.bitfire.davdroid.App
|
import at.bitfire.davdroid.App
|
||||||
import at.bitfire.davdroid.BuildConfig
|
import at.bitfire.davdroid.BuildConfig
|
||||||
import at.bitfire.davdroid.R
|
import at.bitfire.davdroid.R
|
||||||
import at.bitfire.davdroid.settings.SettingsManager
|
import at.bitfire.davdroid.settings.SettingsManager
|
||||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.Model.Companion.HINT_AUTOSTART_PERMISSION
|
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage.Model.Companion.HINT_AUTOSTART_PERMISSION
|
||||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.Model.Companion.HINT_BATTERY_OPTIMIZATIONS
|
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage.Model.Companion.HINT_BATTERY_OPTIMIZATIONS
|
||||||
import at.bitfire.davdroid.ui.widget.SafeAndroidUriHandler
|
import at.bitfire.davdroid.ui.widget.SafeAndroidUriHandler
|
||||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||||
import dagger.Binds
|
import dagger.hilt.EntryPoint
|
||||||
import dagger.Module
|
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.multibindings.IntoSet
|
import dagger.hilt.components.SingletonComponent
|
||||||
import org.apache.commons.text.WordUtils
|
import org.apache.commons.text.WordUtils
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
class BatteryOptimizationsPage: IntroPage {
|
||||||
class BatteryOptimizationsFragment: Fragment() {
|
|
||||||
|
|
||||||
val model by viewModels<Model>()
|
@EntryPoint
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
interface BatteryOptimizationsPageEntryPoint {
|
||||||
|
fun settingsManager(): SettingsManager
|
||||||
|
}
|
||||||
|
|
||||||
private val ignoreBatteryOptimizationsResultLauncher =
|
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
|
||||||
registerForActivityResult(IgnoreBatteryOptimizationsContract) {
|
val settingsManager = EntryPointAccessors.fromApplication(application, BatteryOptimizationsPageEntryPoint::class.java).settingsManager()
|
||||||
|
|
||||||
|
// show fragment when:
|
||||||
|
// 1. DAVx5 is not whitelisted yet and "don't show anymore" has not been clicked, and/or
|
||||||
|
// 2a. evil manufacturer AND
|
||||||
|
// 2b. "don't show anymore" has not been clicked
|
||||||
|
return if (
|
||||||
|
(!Model.isExempted(application) && settingsManager.getBooleanOrNull(HINT_BATTERY_OPTIMIZATIONS) != false) ||
|
||||||
|
(Model.manufacturerWarning && settingsManager.getBooleanOrNull(HINT_AUTOSTART_PERMISSION) != false)
|
||||||
|
)
|
||||||
|
IntroPage.ShowPolicy.SHOW_ALWAYS
|
||||||
|
else
|
||||||
|
IntroPage.ShowPolicy.DONT_SHOW
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun ComposePage() {
|
||||||
|
IntroPage_FromModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun IntroPage_FromModel(
|
||||||
|
model: Model = viewModel()
|
||||||
|
) {
|
||||||
|
val ignoreBatteryOptimizationsResultLauncher = rememberLauncherForActivityResult(IgnoreBatteryOptimizationsContract) {
|
||||||
model.checkBatteryOptimizations()
|
model.checkBatteryOptimizations()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hintBatteryOptimizations by model.hintBatteryOptimizations.observeAsState()
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
val shouldBeExempted by model.shouldBeExempted.observeAsState(false)
|
||||||
return ComposeView(requireContext()).apply {
|
val isExempted by model.isExempted.observeAsState(false)
|
||||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
LaunchedEffect(shouldBeExempted, isExempted) {
|
||||||
setContent {
|
if (shouldBeExempted && !isExempted)
|
||||||
MdcTheme {
|
ignoreBatteryOptimizationsResultLauncher.launch(BuildConfig.APPLICATION_ID)
|
||||||
val hintBatteryOptimizations by model.hintBatteryOptimizations.observeAsState()
|
|
||||||
val shouldBeExempted by model.shouldBeExempted.observeAsState(false)
|
|
||||||
val isExempted by model.isExempted.observeAsState(false)
|
|
||||||
LaunchedEffect(shouldBeExempted, isExempted) {
|
|
||||||
if (shouldBeExempted && !isExempted)
|
|
||||||
ignoreBatteryOptimizationsResultLauncher.launch(BuildConfig.APPLICATION_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
val hintAutostartPermission by model.hintAutostartPermission.observeAsState()
|
|
||||||
val uriHandler = SafeAndroidUriHandler(LocalContext.current)
|
|
||||||
CompositionLocalProvider(LocalUriHandler provides uriHandler) {
|
|
||||||
BatteryOptimizationsContent(
|
|
||||||
dontShowBattery = hintBatteryOptimizations == false,
|
|
||||||
onChangeDontShowBattery = {
|
|
||||||
model.hintBatteryOptimizations.value = !it
|
|
||||||
},
|
|
||||||
isExempted = isExempted,
|
|
||||||
shouldBeExempted = shouldBeExempted,
|
|
||||||
onChangeShouldBeExempted = model.shouldBeExempted::postValue,
|
|
||||||
dontShowAutostart = hintAutostartPermission == false,
|
|
||||||
onChangeDontShowAutostart = {
|
|
||||||
model.hintAutostartPermission.value = !it
|
|
||||||
},
|
|
||||||
manufacturerWarning = Model.manufacturerWarning
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
val hintAutostartPermission by model.hintAutostartPermission.observeAsState()
|
||||||
super.onResume()
|
val uriHandler = SafeAndroidUriHandler(LocalContext.current)
|
||||||
model.checkBatteryOptimizations()
|
CompositionLocalProvider(LocalUriHandler provides uriHandler) {
|
||||||
|
BatteryOptimizationsContent(
|
||||||
|
dontShowBattery = hintBatteryOptimizations == false,
|
||||||
|
onChangeDontShowBattery = {
|
||||||
|
model.hintBatteryOptimizations.value = !it
|
||||||
|
},
|
||||||
|
isExempted = isExempted,
|
||||||
|
shouldBeExempted = shouldBeExempted,
|
||||||
|
onChangeShouldBeExempted = model.shouldBeExempted::postValue,
|
||||||
|
dontShowAutostart = hintAutostartPermission == false,
|
||||||
|
onChangeDontShowAutostart = {
|
||||||
|
model.hintAutostartPermission.value = !it
|
||||||
|
},
|
||||||
|
manufacturerWarning = Model.manufacturerWarning
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class Model @Inject constructor(
|
class Model @Inject constructor(
|
||||||
application: Application,
|
val context: Application,
|
||||||
val settings: SettingsManager
|
val settings: SettingsManager
|
||||||
): AndroidViewModel(application) {
|
): ViewModel() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
@ -178,8 +185,26 @@ class BatteryOptimizationsFragment: Fragment() {
|
||||||
|
|
||||||
val hintAutostartPermission = settings.getBooleanLive(HINT_AUTOSTART_PERMISSION)
|
val hintAutostartPermission = settings.getBooleanLive(HINT_AUTOSTART_PERMISSION)
|
||||||
|
|
||||||
|
private val batteryOptimizationsReceiver = object: BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
checkBatteryOptimizations()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// There's an undocumented intent that is sent when the battery optimization whitelist changes.
|
||||||
|
val intentFilter = IntentFilter("android.os.action.POWER_SAVE_WHITELIST_CHANGED")
|
||||||
|
context.registerReceiver(batteryOptimizationsReceiver, intentFilter)
|
||||||
|
|
||||||
|
checkBatteryOptimizations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
context.unregisterReceiver(batteryOptimizationsReceiver)
|
||||||
|
}
|
||||||
|
|
||||||
fun checkBatteryOptimizations() {
|
fun checkBatteryOptimizations() {
|
||||||
val exempted = isExempted(getApplication())
|
val exempted = isExempted(context)
|
||||||
isExempted.value = exempted
|
isExempted.value = exempted
|
||||||
shouldBeExempted.value = exempted
|
shouldBeExempted.value = exempted
|
||||||
|
|
||||||
|
@ -205,34 +230,6 @@ class BatteryOptimizationsFragment: Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(ActivityComponent::class)
|
|
||||||
abstract class BatteryOptimizationsFragmentModule {
|
|
||||||
@Binds @IntoSet
|
|
||||||
abstract fun getFactory(factory: Factory): IntroFragmentFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
class Factory @Inject constructor(
|
|
||||||
val settingsManager: SettingsManager
|
|
||||||
): IntroFragmentFactory {
|
|
||||||
|
|
||||||
override fun getOrder(context: Context) =
|
|
||||||
// show fragment when:
|
|
||||||
// 1. DAVx5 is not whitelisted yet and "don't show anymore" has not been clicked, and/or
|
|
||||||
// 2a. evil manufacturer AND
|
|
||||||
// 2b. "don't show anymore" has not been clicked
|
|
||||||
if (
|
|
||||||
(!Model.isExempted(context) && settingsManager.getBooleanOrNull(HINT_BATTERY_OPTIMIZATIONS) != false) ||
|
|
||||||
(Model.manufacturerWarning && settingsManager.getBooleanOrNull(HINT_AUTOSTART_PERMISSION) != false)
|
|
||||||
)
|
|
||||||
100
|
|
||||||
else
|
|
||||||
IntroFragmentFactory.DONT_SHOW
|
|
||||||
|
|
||||||
override fun create() = BatteryOptimizationsFragment()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showBackground = true, showSystemUi = true)
|
@Preview(showBackground = true, showSystemUi = true)
|
||||||
|
@ -391,4 +388,5 @@ private fun BatteryOptimizationsContent(
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(90.dp))
|
Spacer(modifier = Modifier.height(90.dp))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
|
@ -5,65 +5,63 @@
|
||||||
package at.bitfire.davdroid.ui.intro
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.activity.addCallback
|
import androidx.activity.addCallback
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.ComposeView
|
||||||
|
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||||
|
import androidx.compose.ui.res.dimensionResource
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import at.bitfire.davdroid.OseIntroPageFactory
|
||||||
import at.bitfire.davdroid.R
|
import at.bitfire.davdroid.R
|
||||||
import at.bitfire.davdroid.log.Logger
|
import at.bitfire.davdroid.log.Logger
|
||||||
import com.github.appintro.AppIntro2
|
import com.github.appintro.AppIntro2
|
||||||
import dagger.hilt.EntryPoint
|
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dagger.hilt.android.EntryPointAccessors
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class IntroActivity: AppIntro2() {
|
class IntroActivity : AppIntro2() {
|
||||||
|
|
||||||
@EntryPoint
|
|
||||||
@InstallIn(ActivityComponent::class)
|
|
||||||
interface IntroActivityEntryPoint {
|
|
||||||
fun introFragmentFactories(): Set<@JvmSuppressWildcards IntroFragmentFactory>
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun shouldShowIntroActivity(activity: Activity): Boolean {
|
fun shouldShowIntroActivity(activity: Activity): Boolean {
|
||||||
val factories = EntryPointAccessors.fromActivity(activity, IntroActivityEntryPoint::class.java).introFragmentFactories()
|
val pages = OseIntroPageFactory().introPages
|
||||||
return factories.any {
|
return pages.any {
|
||||||
it.getOrder(activity) > 0
|
it.getShowPolicy(activity.application) == IntroPage.ShowPolicy.SHOW_ALWAYS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val model by viewModels<Model>()
|
||||||
private var currentSlide = 0
|
private var currentSlide = 0
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val factories = EntryPointAccessors.fromActivity(this, IntroActivityEntryPoint::class.java).introFragmentFactories()
|
model.pages.forEachIndexed { idx, _ ->
|
||||||
for (factory in factories)
|
addSlide(PageFragment().apply {
|
||||||
Logger.log.fine("Found intro fragment factory ${factory::class.java} with order ${factory.getOrder(this)}")
|
arguments = Bundle(1).apply {
|
||||||
|
putInt(PageFragment.ARG_PAGE_IDX, idx)
|
||||||
val factoriesWithOrder = factories
|
}
|
||||||
.associateWith { it.getOrder(this) }
|
})
|
||||||
.filterValues { it != IntroFragmentFactory.DONT_SHOW }
|
|
||||||
|
|
||||||
val anyPositiveOrder = factoriesWithOrder.values.any { it > 0 }
|
|
||||||
if (anyPositiveOrder) {
|
|
||||||
val factoriesSortedByOrder = factoriesWithOrder
|
|
||||||
.toList()
|
|
||||||
.sortedBy { (_, v) -> v } // sort by value (= getOrder())
|
|
||||||
for ((factory, _) in factoriesSortedByOrder)
|
|
||||||
addSlide(factory.create())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setBarColor(ResourcesCompat.getColor(resources, R.color.primaryDarkColor, null))
|
setBarColor(ResourcesCompat.getColor(resources, R.color.primaryDarkColor, null))
|
||||||
|
@ -91,6 +89,31 @@ class IntroActivity: AppIntro2() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class PageFragment: Fragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ARG_PAGE_IDX = "page"
|
||||||
|
}
|
||||||
|
|
||||||
|
val model by activityViewModels<Model>()
|
||||||
|
val page by lazy { model.pages[requireArguments().getInt(ARG_PAGE_IDX)] }
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =
|
||||||
|
ComposeView(requireActivity()).apply {
|
||||||
|
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||||
|
setContent {
|
||||||
|
MdcTheme {
|
||||||
|
Box(Modifier.padding(bottom = dimensionResource(com.github.appintro.R.dimen.appintro2_bottombar_height))) {
|
||||||
|
page.ComposePage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For launching the [IntroActivity]. Result is `true` when the user cancelled the intro.
|
* For launching the [IntroActivity]. Result is `true` when the user cancelled the intro.
|
||||||
*/
|
*/
|
||||||
|
@ -103,4 +126,46 @@ class IntroActivity: AppIntro2() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class Model @Inject constructor(
|
||||||
|
application: Application,
|
||||||
|
introPageFactory: IntroPageFactory
|
||||||
|
): AndroidViewModel(application) {
|
||||||
|
|
||||||
|
private val introPages = introPageFactory.introPages
|
||||||
|
|
||||||
|
private var _pages: List<IntroPage>? = null
|
||||||
|
val pages: List<IntroPage>
|
||||||
|
@Synchronized
|
||||||
|
get() {
|
||||||
|
_pages?.let { return it }
|
||||||
|
|
||||||
|
val newPages = calculatePages()
|
||||||
|
_pages = newPages
|
||||||
|
|
||||||
|
return newPages
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculatePages(): List<IntroPage> {
|
||||||
|
for (page in introPages)
|
||||||
|
Logger.log.fine("Found intro page ${page::class.java} with order ${page.getShowPolicy(getApplication())}")
|
||||||
|
|
||||||
|
val activePages: Map<IntroPage, IntroPage.ShowPolicy> = introPages
|
||||||
|
.associateWith { it.getShowPolicy(getApplication()) }
|
||||||
|
.filterValues { it != IntroPage.ShowPolicy.DONT_SHOW }
|
||||||
|
|
||||||
|
val anyShowAlways = activePages.values.any { it == IntroPage.ShowPolicy.SHOW_ALWAYS }
|
||||||
|
return if (anyShowAlways) {
|
||||||
|
val pages = mutableListOf<IntroPage>()
|
||||||
|
activePages.filterValues { it != IntroPage.ShowPolicy.DONT_SHOW }.forEach { page, _ ->
|
||||||
|
pages += page
|
||||||
|
}
|
||||||
|
pages
|
||||||
|
} else
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,38 +0,0 @@
|
||||||
/***************************************************************************************************
|
|
||||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
|
||||||
**************************************************************************************************/
|
|
||||||
|
|
||||||
package at.bitfire.davdroid.ui.intro
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
|
|
||||||
interface IntroFragmentFactory {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DONT_SHOW = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to determine whether an intro fragment of this type (for instance,
|
|
||||||
* the [BatteryOptimizationsFragment]) should be shown.
|
|
||||||
*
|
|
||||||
* @param context used to determine whether the fragment shall be shown
|
|
||||||
*
|
|
||||||
* @return Order with which an instance of this fragment type shall be created and shown. Possible values:
|
|
||||||
*
|
|
||||||
* * <0: only show the fragment when there is at least one other fragment with positive order (lower numbers are shown first)
|
|
||||||
* * [DONT_SHOW] (0): don't show the fragment
|
|
||||||
* * ≥0: show the fragment (lower numbers are shown first)
|
|
||||||
*/
|
|
||||||
fun getOrder(context: Context): Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance of this intro fragment type. Will only be called when
|
|
||||||
* [getOrder] is true.
|
|
||||||
*
|
|
||||||
* @return the fragment (for instance, a [BatteryOptimizationsFragment]])
|
|
||||||
*/
|
|
||||||
fun create(): Fragment
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||||
|
**************************************************************************************************/
|
||||||
|
|
||||||
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
interface IntroPage {
|
||||||
|
|
||||||
|
enum class ShowPolicy {
|
||||||
|
DONT_SHOW,
|
||||||
|
SHOW_ALWAYS,
|
||||||
|
SHOW_ONLY_WITH_OTHERS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine whether an intro page of this type (for instance,
|
||||||
|
* the [BatteryOptimizationsPage]) should be shown.
|
||||||
|
*
|
||||||
|
* @param application used to determine whether the page shall be shown
|
||||||
|
*
|
||||||
|
* @return Order with which an instance of this page type shall be created and shown. Possible values:
|
||||||
|
*
|
||||||
|
* * < 0: only show the page when there is at least one other page with positive order (lower numbers are shown first)
|
||||||
|
* * [DONT_SHOW] (0): don't show the page
|
||||||
|
* * ≥ 0: show the page (lower numbers are shown first)
|
||||||
|
*/
|
||||||
|
fun getShowPolicy(application: Application): ShowPolicy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composes this page. Will only be called when [getShowPolicy] is not [DONT_SHOW].
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ComposePage()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
|
interface IntroPageFactory {
|
||||||
|
|
||||||
|
val introPages: Array<IntroPage>
|
||||||
|
|
||||||
|
}
|
|
@ -4,11 +4,7 @@
|
||||||
|
|
||||||
package at.bitfire.davdroid.ui.intro
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
import android.content.Context
|
import android.app.Application
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
@ -31,67 +27,75 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.databinding.ObservableBoolean
|
import androidx.databinding.ObservableBoolean
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import at.bitfire.davdroid.App
|
import at.bitfire.davdroid.App
|
||||||
import at.bitfire.davdroid.R
|
import at.bitfire.davdroid.R
|
||||||
import at.bitfire.davdroid.settings.SettingsManager
|
import at.bitfire.davdroid.settings.SettingsManager
|
||||||
import at.bitfire.davdroid.ui.intro.OpenSourceFragment.Model.Companion.SETTING_NEXT_DONATION_POPUP
|
|
||||||
import at.bitfire.davdroid.ui.widget.CardWithImage
|
import at.bitfire.davdroid.ui.widget.CardWithImage
|
||||||
import at.bitfire.davdroid.ui.widget.SafeAndroidUriHandler
|
import at.bitfire.davdroid.ui.widget.SafeAndroidUriHandler
|
||||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
import dagger.hilt.EntryPoint
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
class OpenSourcePage : IntroPage {
|
||||||
class OpenSourceFragment: Fragment() {
|
|
||||||
|
|
||||||
val model by viewModels<Model>()
|
@EntryPoint
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
interface OpenSourcePageEntryPoint {
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
fun settingsManager(): SettingsManager
|
||||||
return ComposeView(requireContext()).apply {
|
|
||||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
||||||
setContent {
|
|
||||||
MdcTheme {
|
|
||||||
var dontShow by remember { mutableStateOf(model.dontShow.get()) }
|
|
||||||
|
|
||||||
val uriHandler = SafeAndroidUriHandler(LocalContext.current)
|
|
||||||
CompositionLocalProvider(LocalUriHandler provides uriHandler) {
|
|
||||||
FragmentContent(
|
|
||||||
dontShow = dontShow,
|
|
||||||
onChangeDontShow = {
|
|
||||||
model.dontShow.set(it)
|
|
||||||
dontShow = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
|
||||||
|
val settingsManager = EntryPointAccessors.fromApplication(application, OpenSourcePageEntryPoint::class.java).settingsManager()
|
||||||
|
|
||||||
|
return if (System.currentTimeMillis() > (settingsManager.getLongOrNull(Model.SETTING_NEXT_DONATION_POPUP) ?: 0))
|
||||||
|
IntroPage.ShowPolicy.SHOW_ALWAYS
|
||||||
|
else
|
||||||
|
IntroPage.ShowPolicy.DONT_SHOW
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun ComposePage() {
|
||||||
|
Page()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Page(model: Model = viewModel()) {
|
||||||
|
var dontShow by remember { mutableStateOf(model.dontShow.get()) }
|
||||||
|
|
||||||
|
val uriHandler = SafeAndroidUriHandler(LocalContext.current)
|
||||||
|
CompositionLocalProvider(LocalUriHandler provides uriHandler) {
|
||||||
|
PageContent(
|
||||||
|
dontShow = dontShow,
|
||||||
|
onChangeDontShow = {
|
||||||
|
model.dontShow.set(it)
|
||||||
|
dontShow = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(
|
@Preview(
|
||||||
showBackground = true,
|
showBackground = true,
|
||||||
showSystemUi = true
|
showSystemUi = true
|
||||||
)
|
)
|
||||||
@Composable
|
@Composable
|
||||||
fun FragmentContent(
|
fun PageContent(
|
||||||
dontShow: Boolean = false,
|
dontShow: Boolean = false,
|
||||||
onChangeDontShow: (Boolean) -> Unit = {}
|
onChangeDontShow: (Boolean) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
@ -112,7 +116,7 @@ class OpenSourceFragment: Fragment() {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
uriHandler.openUri(
|
uriHandler.openUri(
|
||||||
App.homepageUrl(requireActivity())
|
App.homepageUrl(context)
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.appendPath("donate")
|
.appendPath("donate")
|
||||||
.build()
|
.build()
|
||||||
|
@ -163,19 +167,4 @@ class OpenSourceFragment: Fragment() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Factory @Inject constructor(
|
|
||||||
val settingsManager: SettingsManager
|
|
||||||
): IntroFragmentFactory {
|
|
||||||
|
|
||||||
override fun getOrder(context: Context) =
|
|
||||||
if (System.currentTimeMillis() > (settingsManager.getLongOrNull(SETTING_NEXT_DONATION_POPUP) ?: 0))
|
|
||||||
500
|
|
||||||
else
|
|
||||||
0
|
|
||||||
|
|
||||||
override fun create() = OpenSourceFragment()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,44 +0,0 @@
|
||||||
/***************************************************************************************************
|
|
||||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
|
||||||
**************************************************************************************************/
|
|
||||||
|
|
||||||
package at.bitfire.davdroid.ui.intro
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import at.bitfire.davdroid.R
|
|
||||||
import at.bitfire.davdroid.util.PermissionUtils
|
|
||||||
import at.bitfire.davdroid.util.PermissionUtils.CALENDAR_PERMISSIONS
|
|
||||||
import at.bitfire.davdroid.util.PermissionUtils.CONTACT_PERMISSIONS
|
|
||||||
import at.bitfire.ical4android.TaskProvider
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class PermissionsIntroFragment : Fragment() {
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View =
|
|
||||||
inflater.inflate(R.layout.intro_permissions, container, false)
|
|
||||||
|
|
||||||
|
|
||||||
class Factory @Inject constructor(): IntroFragmentFactory {
|
|
||||||
|
|
||||||
override fun getOrder(context: Context): Int {
|
|
||||||
// show PermissionsFragment as intro fragment when no permissions are granted
|
|
||||||
val permissions = CONTACT_PERMISSIONS + CALENDAR_PERMISSIONS +
|
|
||||||
TaskProvider.PERMISSIONS_JTX +
|
|
||||||
TaskProvider.PERMISSIONS_OPENTASKS +
|
|
||||||
TaskProvider.PERMISSIONS_TASKS_ORG
|
|
||||||
return if (PermissionUtils.haveAnyPermission(context, permissions))
|
|
||||||
IntroFragmentFactory.DONT_SHOW
|
|
||||||
else
|
|
||||||
50
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun create() = PermissionsIntroFragment()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||||
|
**************************************************************************************************/
|
||||||
|
|
||||||
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import at.bitfire.davdroid.ui.PermissionsFragmentContent
|
||||||
|
import at.bitfire.davdroid.util.PermissionUtils
|
||||||
|
import at.bitfire.davdroid.util.PermissionUtils.CALENDAR_PERMISSIONS
|
||||||
|
import at.bitfire.davdroid.util.PermissionUtils.CONTACT_PERMISSIONS
|
||||||
|
import at.bitfire.ical4android.TaskProvider
|
||||||
|
|
||||||
|
class PermissionsIntroPage: IntroPage {
|
||||||
|
|
||||||
|
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
|
||||||
|
// show PermissionsFragment as intro fragment when no permissions are granted
|
||||||
|
val permissions = CONTACT_PERMISSIONS + CALENDAR_PERMISSIONS +
|
||||||
|
TaskProvider.PERMISSIONS_JTX +
|
||||||
|
TaskProvider.PERMISSIONS_OPENTASKS +
|
||||||
|
TaskProvider.PERMISSIONS_TASKS_ORG
|
||||||
|
return if (PermissionUtils.haveAnyPermission(application, permissions))
|
||||||
|
IntroPage.ShowPolicy.DONT_SHOW
|
||||||
|
else
|
||||||
|
IntroPage.ShowPolicy.SHOW_ALWAYS
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun ComposePage() {
|
||||||
|
PermissionsFragmentContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
/***************************************************************************************************
|
|
||||||
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
|
||||||
**************************************************************************************************/
|
|
||||||
|
|
||||||
package at.bitfire.davdroid.ui.intro
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
|
||||||
import androidx.compose.ui.res.dimensionResource
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.viewModels
|
|
||||||
import at.bitfire.davdroid.resource.TaskUtils
|
|
||||||
import at.bitfire.davdroid.settings.SettingsManager
|
|
||||||
import at.bitfire.davdroid.ui.TasksCard
|
|
||||||
import at.bitfire.davdroid.ui.TasksModel
|
|
||||||
import com.github.appintro.R
|
|
||||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
class TasksIntroFragment : Fragment() {
|
|
||||||
val model: TasksModel by viewModels()
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
|
||||||
return ComposeView(requireContext()).apply {
|
|
||||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
||||||
setContent {
|
|
||||||
MdcTheme {
|
|
||||||
TasksCard(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(bottom = dimensionResource(R.dimen.appintro2_bottombar_height)),
|
|
||||||
model = model
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Factory @Inject constructor(
|
|
||||||
val settingsManager: SettingsManager
|
|
||||||
): IntroFragmentFactory {
|
|
||||||
|
|
||||||
override fun getOrder(context: Context): Int {
|
|
||||||
return if (!TaskUtils.isAvailable(context) && settingsManager.getBooleanOrNull(TasksModel.HINT_OPENTASKS_NOT_INSTALLED) != false)
|
|
||||||
10
|
|
||||||
else
|
|
||||||
IntroFragmentFactory.DONT_SHOW
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun create() = TasksIntroFragment()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/***************************************************************************************************
|
||||||
|
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
|
||||||
|
**************************************************************************************************/
|
||||||
|
|
||||||
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import at.bitfire.davdroid.resource.TaskUtils
|
||||||
|
import at.bitfire.davdroid.settings.SettingsManager
|
||||||
|
import at.bitfire.davdroid.ui.TasksCard
|
||||||
|
import at.bitfire.davdroid.ui.TasksModel
|
||||||
|
import dagger.hilt.EntryPoint
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
|
class TasksIntroPage : IntroPage {
|
||||||
|
|
||||||
|
@EntryPoint
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
interface TasksIntroPageEntryPoint {
|
||||||
|
fun settingsManager(): SettingsManager
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
|
||||||
|
val settingsManager = EntryPointAccessors.fromApplication(application, TasksIntroPageEntryPoint::class.java).settingsManager()
|
||||||
|
|
||||||
|
return if (!TaskUtils.isAvailable(application) && settingsManager.getBooleanOrNull(TasksModel.HINT_OPENTASKS_NOT_INSTALLED) != false)
|
||||||
|
IntroPage.ShowPolicy.SHOW_ALWAYS
|
||||||
|
else
|
||||||
|
IntroPage.ShowPolicy.DONT_SHOW
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun ComposePage() {
|
||||||
|
TasksCard(model = viewModel<TasksModel>())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,12 +4,8 @@
|
||||||
|
|
||||||
package at.bitfire.davdroid.ui.intro
|
package at.bitfire.davdroid.ui.intro
|
||||||
|
|
||||||
import android.content.Context
|
import android.app.Application
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -26,7 +22,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.ComposeView
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.res.colorResource
|
import androidx.compose.ui.res.colorResource
|
||||||
import androidx.compose.ui.res.dimensionResource
|
import androidx.compose.ui.res.dimensionResource
|
||||||
|
@ -36,28 +31,21 @@ import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import at.bitfire.davdroid.R
|
import at.bitfire.davdroid.R
|
||||||
import dagger.Binds
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
|
||||||
import dagger.multibindings.IntoSet
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class WelcomeFragment: Fragment() {
|
class WelcomePage: IntroPage {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun getShowPolicy(application: Application) = IntroPage.ShowPolicy.SHOW_ONLY_WITH_OTHERS
|
||||||
return ComposeView(requireContext()).apply {
|
|
||||||
setContent {
|
@Composable
|
||||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
override fun ComposePage() {
|
||||||
ContentLandscape()
|
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||||
else
|
ContentLandscape()
|
||||||
ContentPortrait()
|
else
|
||||||
}
|
ContentPortrait()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Preview(
|
@Preview(
|
||||||
device = "id:3.7in WVGA (Nexus One)",
|
device = "id:3.7in WVGA (Nexus One)",
|
||||||
showSystemUi = true
|
showSystemUi = true
|
||||||
|
@ -164,20 +152,4 @@ class WelcomeFragment: Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@Module
|
|
||||||
@InstallIn(ActivityComponent::class)
|
|
||||||
abstract class WelcomeFragmentModule {
|
|
||||||
@Binds @IntoSet
|
|
||||||
abstract fun getFactory(factory: Factory): IntroFragmentFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
class Factory @Inject constructor() : IntroFragmentFactory {
|
|
||||||
|
|
||||||
override fun getOrder(context: Context) = -1000
|
|
||||||
|
|
||||||
override fun create() = WelcomeFragment()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
tools:context=".ui.account.AccountActivity">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:theme="?attr/actionBarTheme">
|
|
||||||
|
|
||||||
<androidx.appcompat.widget.Toolbar
|
|
||||||
android:id="@+id/toolbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?attr/actionBarSize"
|
|
||||||
app:popupTheme="?attr/actionBarPopupTheme" />
|
|
||||||
|
|
||||||
<com.google.android.material.tabs.TabLayout
|
|
||||||
android:id="@+id/tab_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
style="@style/Widget.MaterialComponents.TabLayout.Colored" />
|
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/view_pager"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_behavior="android.support.design.widget.AppBarLayout$ScrollingViewBehavior" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/refresh"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="top|end"
|
|
||||||
android:layout_margin="@dimen/fab_margin"
|
|
||||||
app:backgroundTint="@android:color/white"
|
|
||||||
app:tint="@color/grey900"
|
|
||||||
app:srcCompat="@drawable/ic_folder_refresh_outline"
|
|
||||||
app:layout_anchor="@id/sync"
|
|
||||||
app:layout_anchorGravity="top|center"
|
|
||||||
tools:ignore="ContentDescription" />
|
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
android:id="@+id/sync"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom|end"
|
|
||||||
android:layout_margin="@dimen/fab_margin"
|
|
||||||
android:contentDescription="@string/account_synchronize_now"
|
|
||||||
app:useCompatPadding="true"
|
|
||||||
app:srcCompat="@drawable/ic_sync" />
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingBottom="@dimen/appintro2_bottombar_height">
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
|
||||||
android:id="@+id/frame_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:name="at.bitfire.davdroid.ui.PermissionsFragment"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
|
@ -8,14 +8,15 @@ import at.bitfire.davdroid.ui.AboutActivity
|
||||||
import at.bitfire.davdroid.ui.AccountsDrawerHandler
|
import at.bitfire.davdroid.ui.AccountsDrawerHandler
|
||||||
import at.bitfire.davdroid.ui.OpenSourceLicenseInfoProvider
|
import at.bitfire.davdroid.ui.OpenSourceLicenseInfoProvider
|
||||||
import at.bitfire.davdroid.ui.OseAccountsDrawerHandler
|
import at.bitfire.davdroid.ui.OseAccountsDrawerHandler
|
||||||
import at.bitfire.davdroid.ui.intro.IntroFragmentFactory
|
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
|
||||||
import at.bitfire.davdroid.ui.intro.OpenSourceFragment
|
import at.bitfire.davdroid.ui.intro.IntroPage
|
||||||
import at.bitfire.davdroid.ui.intro.PermissionsIntroFragment
|
import at.bitfire.davdroid.ui.intro.IntroPageFactory
|
||||||
import at.bitfire.davdroid.ui.intro.TasksIntroFragment
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.components.ActivityComponent
|
import dagger.hilt.android.components.ActivityComponent
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
|
|
||||||
interface OseFlavorModules {
|
interface OseFlavorModules {
|
||||||
|
@ -30,28 +31,22 @@ interface OseFlavorModules {
|
||||||
fun appLicenseInfoProvider(impl: OpenSourceLicenseInfoProvider): AboutActivity.AppLicenseInfoProvider
|
fun appLicenseInfoProvider(impl: OpenSourceLicenseInfoProvider): AboutActivity.AppLicenseInfoProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//// intro fragments ////
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(ActivityComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface OpenSourceFragmentModule {
|
interface Global {
|
||||||
@Binds @IntoSet
|
@Binds
|
||||||
fun getFactory(factory: OpenSourceFragment.Factory): IntroFragmentFactory
|
fun introPageFactory(impl: OseIntroPageFactory): IntroPageFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(ActivityComponent::class)
|
//// intro pages ////
|
||||||
interface PermissionsIntroFragmentModule {
|
|
||||||
@Binds @IntoSet
|
|
||||||
fun getFactory(factory: PermissionsIntroFragment.Factory): IntroFragmentFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(ActivityComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
interface TasksIntroFragmentModule {
|
interface IntroPagesModule {
|
||||||
@Binds @IntoSet
|
@Provides
|
||||||
fun getFactory(factory: TasksIntroFragment.Factory): IntroFragmentFactory
|
@IntoSet
|
||||||
|
fun introPage(): IntroPage = BatteryOptimizationsPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package at.bitfire.davdroid
|
||||||
|
|
||||||
|
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
|
||||||
|
import at.bitfire.davdroid.ui.intro.IntroPageFactory
|
||||||
|
import at.bitfire.davdroid.ui.intro.OpenSourcePage
|
||||||
|
import at.bitfire.davdroid.ui.intro.PermissionsIntroPage
|
||||||
|
import at.bitfire.davdroid.ui.intro.TasksIntroPage
|
||||||
|
import at.bitfire.davdroid.ui.intro.WelcomePage
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class OseIntroPageFactory @Inject constructor(): IntroPageFactory {
|
||||||
|
|
||||||
|
override val introPages = arrayOf(
|
||||||
|
WelcomePage(),
|
||||||
|
TasksIntroPage(),
|
||||||
|
PermissionsIntroPage(),
|
||||||
|
BatteryOptimizationsPage(),
|
||||||
|
OpenSourcePage()
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue