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.settings.Settings
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment
|
||||
import at.bitfire.davdroid.ui.intro.OpenSourceFragment
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
@ -272,10 +271,10 @@ class AppSettingsActivity: AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun resetHints() {
|
||||
settings.remove(BatteryOptimizationsFragment.Model.HINT_BATTERY_OPTIMIZATIONS)
|
||||
settings.remove(BatteryOptimizationsFragment.Model.HINT_AUTOSTART_PERMISSION)
|
||||
settings.remove(BatteryOptimizationsPage.Model.HINT_BATTERY_OPTIMIZATIONS)
|
||||
settings.remove(BatteryOptimizationsPage.Model.HINT_AUTOSTART_PERMISSION)
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -6,15 +6,14 @@ package at.bitfire.davdroid.ui.intro
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
|
@ -40,53 +39,69 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.ViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.App
|
||||
import at.bitfire.davdroid.BuildConfig
|
||||
import at.bitfire.davdroid.R
|
||||
import at.bitfire.davdroid.settings.SettingsManager
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsFragment.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_AUTOSTART_PERMISSION
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage.Model.Companion.HINT_BATTERY_OPTIMIZATIONS
|
||||
import at.bitfire.davdroid.ui.widget.SafeAndroidUriHandler
|
||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.multibindings.IntoSet
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import org.apache.commons.text.WordUtils
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BatteryOptimizationsFragment: Fragment() {
|
||||
class BatteryOptimizationsPage: IntroPage {
|
||||
|
||||
val model by viewModels<Model>()
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface BatteryOptimizationsPageEntryPoint {
|
||||
fun settingsManager(): SettingsManager
|
||||
}
|
||||
|
||||
private val ignoreBatteryOptimizationsResultLauncher =
|
||||
registerForActivityResult(IgnoreBatteryOptimizationsContract) {
|
||||
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
MdcTheme {
|
||||
val hintBatteryOptimizations by model.hintBatteryOptimizations.observeAsState()
|
||||
val shouldBeExempted by model.shouldBeExempted.observeAsState(false)
|
||||
val isExempted by model.isExempted.observeAsState(false)
|
||||
|
@ -114,21 +129,13 @@ class BatteryOptimizationsFragment: Fragment() {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
model.checkBatteryOptimizations()
|
||||
}
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
class Model @Inject constructor(
|
||||
application: Application,
|
||||
val context: Application,
|
||||
val settings: SettingsManager
|
||||
): AndroidViewModel(application) {
|
||||
): ViewModel() {
|
||||
|
||||
companion object {
|
||||
|
||||
|
@ -178,8 +185,26 @@ class BatteryOptimizationsFragment: Fragment() {
|
|||
|
||||
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() {
|
||||
val exempted = isExempted(getApplication())
|
||||
val exempted = isExempted(context)
|
||||
isExempted.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)
|
||||
|
@ -391,4 +388,5 @@ private fun BatteryOptimizationsContent(
|
|||
)
|
||||
Spacer(modifier = Modifier.height(90.dp))
|
||||
}
|
||||
|
||||
}
|
|
@ -5,65 +5,63 @@
|
|||
package at.bitfire.davdroid.ui.intro
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import androidx.activity.viewModels
|
||||
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.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.log.Logger
|
||||
import com.github.appintro.AppIntro2
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class IntroActivity : AppIntro2() {
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(ActivityComponent::class)
|
||||
interface IntroActivityEntryPoint {
|
||||
fun introFragmentFactories(): Set<@JvmSuppressWildcards IntroFragmentFactory>
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@WorkerThread
|
||||
fun shouldShowIntroActivity(activity: Activity): Boolean {
|
||||
val factories = EntryPointAccessors.fromActivity(activity, IntroActivityEntryPoint::class.java).introFragmentFactories()
|
||||
return factories.any {
|
||||
it.getOrder(activity) > 0
|
||||
val pages = OseIntroPageFactory().introPages
|
||||
return pages.any {
|
||||
it.getShowPolicy(activity.application) == IntroPage.ShowPolicy.SHOW_ALWAYS
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val model by viewModels<Model>()
|
||||
private var currentSlide = 0
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val factories = EntryPointAccessors.fromActivity(this, IntroActivityEntryPoint::class.java).introFragmentFactories()
|
||||
for (factory in factories)
|
||||
Logger.log.fine("Found intro fragment factory ${factory::class.java} with order ${factory.getOrder(this)}")
|
||||
|
||||
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())
|
||||
model.pages.forEachIndexed { idx, _ ->
|
||||
addSlide(PageFragment().apply {
|
||||
arguments = Bundle(1).apply {
|
||||
putInt(PageFragment.ARG_PAGE_IDX, idx)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.app.Application
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
@ -31,45 +27,56 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.databinding.ObservableBoolean
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import at.bitfire.davdroid.App
|
||||
import at.bitfire.davdroid.R
|
||||
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.SafeAndroidUriHandler
|
||||
import com.google.accompanist.themeadapter.material.MdcTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class OpenSourceFragment: Fragment() {
|
||||
class OpenSourcePage : IntroPage {
|
||||
|
||||
val model by viewModels<Model>()
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface OpenSourcePageEntryPoint {
|
||||
fun settingsManager(): SettingsManager
|
||||
}
|
||||
|
||||
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
|
||||
val settingsManager = EntryPointAccessors.fromApplication(application, OpenSourcePageEntryPoint::class.java).settingsManager()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
MdcTheme {
|
||||
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) {
|
||||
FragmentContent(
|
||||
PageContent(
|
||||
dontShow = dontShow,
|
||||
onChangeDontShow = {
|
||||
model.dontShow.set(it)
|
||||
|
@ -78,20 +85,17 @@ class OpenSourceFragment: Fragment() {
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(
|
||||
showBackground = true,
|
||||
showSystemUi = true
|
||||
)
|
||||
@Composable
|
||||
fun FragmentContent(
|
||||
fun PageContent(
|
||||
dontShow: Boolean = false,
|
||||
onChangeDontShow: (Boolean) -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
Column(
|
||||
|
@ -112,7 +116,7 @@ class OpenSourceFragment: Fragment() {
|
|||
OutlinedButton(
|
||||
onClick = {
|
||||
uriHandler.openUri(
|
||||
App.homepageUrl(requireActivity())
|
||||
App.homepageUrl(context)
|
||||
.buildUpon()
|
||||
.appendPath("donate")
|
||||
.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
|
||||
|
||||
import android.content.Context
|
||||
import android.app.Application
|
||||
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.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
@ -26,7 +22,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
|
@ -36,27 +31,20 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.fragment.app.Fragment
|
||||
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 {
|
||||
return ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
override fun getShowPolicy(application: Application) = IntroPage.ShowPolicy.SHOW_ONLY_WITH_OTHERS
|
||||
|
||||
@Composable
|
||||
override fun ComposePage() {
|
||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
ContentLandscape()
|
||||
else
|
||||
ContentPortrait()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(
|
||||
device = "id:3.7in WVGA (Nexus One)",
|
||||
|
@ -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.OpenSourceLicenseInfoProvider
|
||||
import at.bitfire.davdroid.ui.OseAccountsDrawerHandler
|
||||
import at.bitfire.davdroid.ui.intro.IntroFragmentFactory
|
||||
import at.bitfire.davdroid.ui.intro.OpenSourceFragment
|
||||
import at.bitfire.davdroid.ui.intro.PermissionsIntroFragment
|
||||
import at.bitfire.davdroid.ui.intro.TasksIntroFragment
|
||||
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
|
||||
import at.bitfire.davdroid.ui.intro.IntroPage
|
||||
import at.bitfire.davdroid.ui.intro.IntroPageFactory
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.components.ActivityComponent
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.IntoSet
|
||||
|
||||
interface OseFlavorModules {
|
||||
|
@ -30,28 +31,22 @@ interface OseFlavorModules {
|
|||
fun appLicenseInfoProvider(impl: OpenSourceLicenseInfoProvider): AboutActivity.AppLicenseInfoProvider
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface Global {
|
||||
@Binds
|
||||
fun introPageFactory(impl: OseIntroPageFactory): IntroPageFactory
|
||||
}
|
||||
|
||||
//// intro fragments ////
|
||||
|
||||
//// intro pages ////
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
interface OpenSourceFragmentModule {
|
||||
@Binds @IntoSet
|
||||
fun getFactory(factory: OpenSourceFragment.Factory): IntroFragmentFactory
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
interface PermissionsIntroFragmentModule {
|
||||
@Binds @IntoSet
|
||||
fun getFactory(factory: PermissionsIntroFragment.Factory): IntroFragmentFactory
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(ActivityComponent::class)
|
||||
interface TasksIntroFragmentModule {
|
||||
@Binds @IntoSet
|
||||
fun getFactory(factory: TasksIntroFragment.Factory): IntroFragmentFactory
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface IntroPagesModule {
|
||||
@Provides
|
||||
@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