Add option for daily rules

This commit is contained in:
Jonas Lochmann 2020-11-23 01:00:00 +01:00
parent 4ddd79bc94
commit 8fb9f496aa
No known key found for this signature in database
GPG key ID: 8B8C9AEE10FA5B36
19 changed files with 1375 additions and 149 deletions

File diff suppressed because it is too large Load diff

View file

@ -250,4 +250,10 @@ object DatabaseMigrations {
database.execSQL("ALTER TABLE `category` ADD COLUMN `tasks_version` TEXT NOT NULL DEFAULT ''")
}
}
val MIGRATE_TO_V35 = object: Migration(34, 35) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `time_limit_rule` ADD COLUMN `per_day` INTEGER NOT NULL DEFAULT 0")
}
}
}

View file

@ -50,7 +50,7 @@ import java.util.concurrent.TimeUnit
UserLimitLoginCategory::class,
CategoryNetworkId::class,
ChildTask::class
], version = 34)
], version = 35)
abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database {
companion object {
private val lock = Object()
@ -118,7 +118,8 @@ abstract class RoomDatabase: RoomDatabase(), io.timelimit.android.data.Database
DatabaseMigrations.MIGRATE_TO_V31,
DatabaseMigrations.MIGRATE_TO_V32,
DatabaseMigrations.MIGRATE_TO_V33,
DatabaseMigrations.MIGRATE_TO_V34
DatabaseMigrations.MIGRATE_TO_V34,
DatabaseMigrations.MIGRATE_TO_V35
)
.setQueryExecutor(Threads.database)
.build()

View file

@ -211,7 +211,7 @@ object HintsToShow {
const val CATEGORIES_INTRODUCTION = 4L
const val TIME_LIMIT_RULE_INTRODUCTION = 8L
const val CONTACTS_INTRO = 16L
const val TIMELIMIT_RULE_MUSTREAD = 32L
private const val OBSOLETE_TIMELIMIT_RULE_MUSTREAD = 32L
const val BLOCKED_TIME_AREAS_OBSOLETE = 64L
const val TASKS_INTRODUCTION = 128L
}

View file

@ -53,7 +53,9 @@ data class TimeLimitRule(
@ColumnInfo(name = "session_duration_milliseconds")
val sessionDurationMilliseconds: Int,
@ColumnInfo(name = "session_pause_milliseconds")
val sessionPauseMilliseconds: Int
val sessionPauseMilliseconds: Int,
@ColumnInfo(name = "per_day")
val perDay: Boolean
): Parcelable, JsonSerializable {
companion object {
private const val RULE_ID = "ruleId"
@ -65,6 +67,7 @@ data class TimeLimitRule(
private const val END_MINUTE_OF_DAY = "end"
private const val SESSION_DURATION_MILLISECONDS = "dur"
private const val SESSION_PAUSE_MILLISECONDS = "pause"
private const val PER_DAY = "perDay"
const val MIN_START_MINUTE = MinuteOfDay.MIN
const val MAX_END_MINUTE = MinuteOfDay.MAX
@ -79,6 +82,7 @@ data class TimeLimitRule(
var endMinuteOfDay = MAX_END_MINUTE
var sessionDurationMilliseconds = 0
var sessionPauseMilliseconds = 0
var perDay = false
reader.beginObject()
@ -93,6 +97,7 @@ data class TimeLimitRule(
END_MINUTE_OF_DAY -> endMinuteOfDay = reader.nextInt()
SESSION_DURATION_MILLISECONDS -> sessionDurationMilliseconds = reader.nextInt()
SESSION_PAUSE_MILLISECONDS -> sessionPauseMilliseconds = reader.nextInt()
PER_DAY -> perDay = reader.nextBoolean()
else -> reader.skipValue()
}
}
@ -108,7 +113,8 @@ data class TimeLimitRule(
startMinuteOfDay = startMinuteOfDay,
endMinuteOfDay = endMinuteOfDay,
sessionDurationMilliseconds = sessionDurationMilliseconds,
sessionPauseMilliseconds = sessionPauseMilliseconds
sessionPauseMilliseconds = sessionPauseMilliseconds,
perDay = perDay
)
}
}
@ -159,6 +165,8 @@ data class TimeLimitRule(
writer.name(SESSION_PAUSE_MILLISECONDS).value(sessionPauseMilliseconds)
}
if (perDay) writer.name(PER_DAY).value(true)
writer.endObject()
}
}

View file

@ -20,6 +20,8 @@ import org.threeten.bp.LocalDate
import java.util.*
data class DateInTimezone(val dayOfWeek: Int, val dayOfEpoch: Int, val localDate: LocalDate) {
val firstDayOfWeekAsEpochDay get() = dayOfEpoch - dayOfWeek
companion object {
fun convertDayOfWeek(dayOfWeek: Int) = when(dayOfWeek) {
Calendar.MONDAY -> 0

View file

@ -57,8 +57,8 @@ data class RemainingTime(val includingExtraTime: Long, val default: Long) {
}
val relatedRules = getRulesRelatedToDay(dayOfWeek, minuteOfDay, rules)
val withoutExtraTime = getRemainingTime(usedTimes, relatedRules, false, firstDayOfWeekAsEpochDay)
val withExtraTime = getRemainingTime(usedTimes, relatedRules, true, firstDayOfWeekAsEpochDay)
val withoutExtraTime = getRemainingTime(usedTimes, relatedRules, false, firstDayOfWeekAsEpochDay, dayOfWeek)
val withExtraTime = getRemainingTime(usedTimes, relatedRules, true, firstDayOfWeekAsEpochDay, dayOfWeek)
if (withoutExtraTime == null && withExtraTime == null) {
// no rules
@ -86,15 +86,21 @@ data class RemainingTime(val includingExtraTime: Long, val default: Long) {
}
}
private fun getRemainingTime(usedTimes: List<UsedTimeItem>, relatedRules: List<TimeLimitRule>, assumeMaximalExtraTime: Boolean, firstDayOfWeekAsEpochDay: Int): Long? {
private fun getRemainingTime(usedTimes: List<UsedTimeItem>, relatedRules: List<TimeLimitRule>, assumeMaximalExtraTime: Boolean, firstDayOfWeekAsEpochDay: Int, dayOfWeek: Int): Long? {
return relatedRules.filter { (!assumeMaximalExtraTime) || it.applyToExtraTimeUsage }.map { rule ->
var usedTime = 0L
usedTimes.forEach { usedTimeItem ->
if (usedTimeItem.dayOfEpoch >= firstDayOfWeekAsEpochDay && usedTimeItem.dayOfEpoch <= firstDayOfWeekAsEpochDay + 6) {
val usedTimeItemDayOfWeek = usedTimeItem.dayOfEpoch - firstDayOfWeekAsEpochDay
val doesWeekMatch = usedTimeItem.dayOfEpoch >= firstDayOfWeekAsEpochDay && usedTimeItem.dayOfEpoch <= firstDayOfWeekAsEpochDay + 6
if ((rule.dayMask.toInt() and (1 shl usedTimeItemDayOfWeek)) != 0) {
if (doesWeekMatch) {
val usedTimeItemDayOfWeek = usedTimeItem.dayOfEpoch - firstDayOfWeekAsEpochDay
val doesDayMaskMatch = (rule.dayMask.toInt() and (1 shl usedTimeItemDayOfWeek)) != 0
val doesCurrentDayMatch = dayOfWeek == usedTimeItemDayOfWeek
val usedTimeItemMatching = if (rule.perDay) doesCurrentDayMatch else doesDayMaskMatch
if (usedTimeItemMatching) {
if (rule.startMinuteOfDay == usedTimeItem.startTimeOfDay && rule.endMinuteOfDay == usedTimeItem.endTimeOfDay) {
usedTime += usedTimeItem.usedMillis
}
@ -106,7 +112,7 @@ data class RemainingTime(val includingExtraTime: Long, val default: Long) {
val remaining = Math.max(0, maxTime - usedTime)
remaining
}.min()
}.minOrNull()
}
}
}
}

View file

@ -1442,7 +1442,8 @@ data class CreateTimeLimitRuleAction(val rule: TimeLimitRule): ParentAction() {
data class UpdateTimeLimitRuleAction(
val ruleId: String, val dayMask: Byte, val maximumTimeInMillis: Int, val applyToExtraTimeUsage: Boolean,
val start: Int, val end: Int, val sessionDurationMilliseconds: Int, val sessionPauseMilliseconds: Int
val start: Int, val end: Int, val sessionDurationMilliseconds: Int, val sessionPauseMilliseconds: Int,
val perDay: Boolean
): ParentAction() {
companion object {
const val TYPE_VALUE = "UPDATE_TIMELIMIT_RULE"
@ -1454,6 +1455,7 @@ data class UpdateTimeLimitRuleAction(
private const val END = "end"
private const val SESSION_DURATION_MILLISECONDS = "dur"
private const val SESSION_PAUSE_MILLISECONDS = "pause"
private const val PER_DAY = "perDay"
}
init {
@ -1492,6 +1494,8 @@ data class UpdateTimeLimitRuleAction(
writer.name(SESSION_PAUSE_MILLISECONDS).value(sessionPauseMilliseconds)
}
if (perDay) writer.name(PER_DAY).value(true)
writer.endObject()
}
}

View file

@ -315,7 +315,8 @@ object LocalDatabaseParentActionDispatcher {
startMinuteOfDay = action.start,
endMinuteOfDay = action.end,
sessionDurationMilliseconds = action.sessionDurationMilliseconds,
sessionPauseMilliseconds = action.sessionPauseMilliseconds
sessionPauseMilliseconds = action.sessionPauseMilliseconds,
perDay = action.perDay
))
}
is SetDeviceUserAction -> {

View file

@ -821,7 +821,8 @@ data class ServerTimeLimitRule(
val startMinuteOfDay: Int,
val endMinuteOfDay: Int,
val sessionDurationMilliseconds: Int,
val sessionPauseMilliseconds: Int
val sessionPauseMilliseconds: Int,
val perDay: Boolean
) {
companion object {
private const val ID = "id"
@ -832,6 +833,7 @@ data class ServerTimeLimitRule(
private const val END_MINUTE_OF_DAY = "end"
private const val SESSION_DURATION_MILLISECONDS = "session"
private const val SESSION_PAUSE_MILLISECONDS = "pause"
private const val PER_DAY = "perDay"
fun parse(reader: JsonReader): ServerTimeLimitRule {
var id: String? = null
@ -842,6 +844,7 @@ data class ServerTimeLimitRule(
var endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE
var sessionDurationMilliseconds: Int = 0
var sessionPauseMilliseconds: Int = 0
var perDay = false
reader.beginObject()
while (reader.hasNext()) {
@ -854,6 +857,7 @@ data class ServerTimeLimitRule(
END_MINUTE_OF_DAY -> endMinuteOfDay = reader.nextInt()
SESSION_DURATION_MILLISECONDS -> sessionDurationMilliseconds = reader.nextInt()
SESSION_PAUSE_MILLISECONDS -> sessionPauseMilliseconds = reader.nextInt()
PER_DAY -> perDay = reader.nextBoolean()
else -> reader.skipValue()
}
}
@ -867,7 +871,8 @@ data class ServerTimeLimitRule(
startMinuteOfDay = startMinuteOfDay,
endMinuteOfDay = endMinuteOfDay,
sessionDurationMilliseconds = sessionDurationMilliseconds,
sessionPauseMilliseconds = sessionPauseMilliseconds
sessionPauseMilliseconds = sessionPauseMilliseconds,
perDay = perDay
)
}
@ -893,7 +898,8 @@ data class ServerTimeLimitRule(
startMinuteOfDay = startMinuteOfDay,
endMinuteOfDay = endMinuteOfDay,
sessionDurationMilliseconds = sessionDurationMilliseconds,
sessionPauseMilliseconds = sessionPauseMilliseconds
sessionPauseMilliseconds = sessionPauseMilliseconds,
perDay = perDay
)
}

View file

@ -23,6 +23,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.timelimit.android.R
import io.timelimit.android.data.model.UsedTimeItem
import io.timelimit.android.databinding.*
import io.timelimit.android.date.DateInTimezone
import io.timelimit.android.extensions.MinuteOfDay
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.logic.DummyApps
@ -46,7 +47,7 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
var items: List<AppAndRuleItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
var usedTimes: List<UsedTimeItem> by Delegates.observable(emptyList()) { _, _, _ -> notifyDataSetChanged() }
var epochDayOfStartOfWeek: Int by Delegates.observable(0) { _, _, _ -> notifyDataSetChanged() }
var date: DateInTimezone? by Delegates.observable(null as DateInTimezone?) { _, _, _ -> notifyDataSetChanged() }
var handlers: Handlers? = null
init {
@ -153,13 +154,29 @@ class AppAndRuleAdapter: RecyclerView.Adapter<AppAndRuleAdapter.Holder>() {
val binding = holder.itemView.tag as FragmentCategoryTimeLimitRuleItemBinding
val context = binding.root.context
val dayNames = binding.root.resources.getStringArray(R.array.days_of_week_array)
val usedTime = usedTimes.filter { usedTime ->
val dayOfWeek = usedTime.dayOfEpoch - epochDayOfStartOfWeek
usedTime.startTimeOfDay == rule.startMinuteOfDay && usedTime.endTimeOfDay == rule.endMinuteOfDay &&
(rule.dayMask.toInt() and (1 shl dayOfWeek) != 0)
}.map { it.usedMillis }.sum().toInt()
val usedTime = date?.let { date ->
usedTimes.filter { usedTime ->
val dayOfWeek = usedTime.dayOfEpoch - date.firstDayOfWeekAsEpochDay
val matchingSlot = usedTime.startTimeOfDay == rule.startMinuteOfDay && usedTime.endTimeOfDay == rule.endMinuteOfDay
val matchingMask = (rule.dayMask.toInt() and (1 shl dayOfWeek) != 0)
val matchingDay = dayOfWeek == date.dayOfWeek
binding.maxTimeString = TimeTextUtil.time(rule.maximumTimeInMillis, context)
matchingSlot && (if (rule.perDay) matchingDay else matchingMask)
}.map { it.usedMillis }.sum().toInt()
} ?: 0
binding.maxTimeString = rule.maximumTimeInMillis.let { time ->
val timeString = TimeTextUtil.time(time, context)
val weeklyDailyString = context.getString(
if (rule.perDay) R.string.category_time_limit_rules_per_day
else R.string.category_time_limit_rules_per_week
)
val zeroTime = time == 0
val onlySingleDay = rule.dayMask.countOneBits() <= 1
val hideDailyWeekly = zeroTime || onlySingleDay
if (hideDailyWeekly) timeString else "$weeklyDailyString $timeString"
}
binding.usageAsText = if (rule.maximumTimeInMillis > 0) TimeTextUtil.used(usedTime, context) else null
binding.usageProgressInPercent = if (rule.maximumTimeInMillis > 0)
(usedTime * 100 / rule.maximumTimeInMillis)

View file

@ -44,16 +44,14 @@ class AppsAndRulesModel(application: Application): AndroidViewModel(application)
private val userDayOfWeek = userDate.map { it.dayOfWeek }
val firstDayOfWeekAndUsedTimes = userDate.switchMap { date ->
val firstDayOfWeekAsEpochDay = date.dayOfEpoch - date.dayOfWeek
val dateAndUsedTimes = userDate.switchMap { date ->
categoryIdLive.switchMap { categoryId ->
database.usedTimes().getUsedTimesOfWeek(
categoryId = categoryId,
firstDayOfWeekAsEpochDay = firstDayOfWeekAsEpochDay
firstDayOfWeekAsEpochDay = date.firstDayOfWeekAsEpochDay
)
}.map { usedTimes ->
firstDayOfWeekAsEpochDay to usedTimes
date to usedTimes
}
}

View file

@ -63,8 +63,8 @@ abstract class CategoryAppsAndRulesFragment: Fragment(), Handlers, EditTimeLimit
recycler.layoutManager = LinearLayoutManager(requireContext())
recycler.adapter = adapter
model.firstDayOfWeekAndUsedTimes.observe(viewLifecycleOwner) { (firstDayOfWeek, usedTimes) ->
adapter.epochDayOfStartOfWeek = firstDayOfWeek
model.dateAndUsedTimes.observe(viewLifecycleOwner) { (date, usedTimes) ->
adapter.date = date
adapter.usedTimes = usedTimes
}
@ -124,7 +124,8 @@ abstract class CategoryAppsAndRulesFragment: Fragment(), Handlers, EditTimeLimit
start = oldRule.startMinuteOfDay,
end = oldRule.endMinuteOfDay,
sessionDurationMilliseconds = oldRule.sessionDurationMilliseconds,
sessionPauseMilliseconds = oldRule.sessionPauseMilliseconds
sessionPauseMilliseconds = oldRule.sessionPauseMilliseconds,
perDay = oldRule.perDay
)
)
}

View file

@ -28,22 +28,18 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import io.timelimit.android.R
import io.timelimit.android.async.Threads
import io.timelimit.android.coroutines.runAsync
import io.timelimit.android.data.IdGenerator
import io.timelimit.android.data.model.HintsToShow
import io.timelimit.android.data.model.TimeLimitRule
import io.timelimit.android.data.model.UserType
import io.timelimit.android.databinding.FragmentEditTimeLimitRuleDialogBinding
import io.timelimit.android.extensions.MinuteOfDay
import io.timelimit.android.extensions.showSafe
import io.timelimit.android.livedata.waitForNonNullValue
import io.timelimit.android.logic.DefaultAppLogic
import io.timelimit.android.sync.actions.CreateTimeLimitRuleAction
import io.timelimit.android.sync.actions.DeleteTimeLimitRuleAction
import io.timelimit.android.sync.actions.UpdateTimeLimitRuleAction
import io.timelimit.android.ui.main.ActivityViewModel
import io.timelimit.android.ui.main.getActivityViewModel
import io.timelimit.android.ui.mustread.MustReadFragment
import io.timelimit.android.ui.view.SelectDayViewHandlers
import io.timelimit.android.ui.view.SelectTimeSpanViewListener
import io.timelimit.android.util.TimeTextUtil
@ -86,30 +82,14 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment(), DurationPic
if (existingRule != null) {
existingRule!!.categoryId
} else {
arguments!!.getString(PARAM_CATEGORY_ID)!!
requireArguments().getString(PARAM_CATEGORY_ID)!!
}
}
private val auth: ActivityViewModel by lazy { getActivityViewModel(activity!!) }
private val auth: ActivityViewModel by lazy { getActivityViewModel(requireActivity()) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val database = DefaultAppLogic.with(context!!).database
runAsync {
val wasShown = database.config().wereHintsShown(HintsToShow.TIMELIMIT_RULE_MUSTREAD).waitForNonNullValue()
if (!wasShown) {
MustReadFragment.newInstance(io.timelimit.android.R.string.must_read_timelimit_rules).show(fragmentManager!!)
Threads.database.execute {
database.config().setHintsShownSync(HintsToShow.TIMELIMIT_RULE_MUSTREAD)
}
}
}
}
existingRule = savedInstanceState?.getParcelable(PARAM_EXISTING_RULE)
?: arguments?.getParcelable<TimeLimitRule?>(PARAM_EXISTING_RULE)
}
@ -124,23 +104,28 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment(), DurationPic
)
view.applyToExtraTime = newRule.applyToExtraTimeUsage
val affectedDays = Math.max(0, (0..6).map { (newRule.dayMask.toInt() shr it) and 1 }.sum())
view.timeSpan.maxDays = Math.max(0, affectedDays - 1) // max prevents crash
val affectedDays = (0..6).map { (newRule.dayMask.toInt() shr it) and 1 }.sum()
val maxDayDuration = if (newRule.perDay) 1 else affectedDays
view.timeSpan.maxDays = 0.coerceAtLeast(maxDayDuration - 1)
view.timeSpan.timeInMillis = newRule.maximumTimeInMillis.toLong()
view.affectsMultipleDays = affectedDays >= 2
view.showWeeklyDailyOption = affectedDays >= 2 && newRule.maximumTimeInMillis > 0
view.applyToWholeDay = newRule.appliesToWholeDay
view.startTime = MinuteOfDay.format(newRule.startMinuteOfDay)
view.endTime = MinuteOfDay.format(newRule.endMinuteOfDay)
view.enableSessionDurationLimit = newRule.sessionDurationLimitEnabled
view.sessionBreakText = TimeTextUtil.minutes(newRule.sessionPauseMilliseconds / (1000 * 60), context!!)
view.sessionLengthText = TimeTextUtil.minutes(newRule.sessionDurationMilliseconds / (1000 * 60), context!!)
view.sessionBreakText = TimeTextUtil.minutes(newRule.sessionPauseMilliseconds / (1000 * 60), requireContext())
view.sessionLengthText = TimeTextUtil.minutes(newRule.sessionDurationMilliseconds / (1000 * 60), requireContext())
view.typePerDay.isChecked = newRule.perDay
view.typePerWeek.isChecked = !newRule.perDay
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val listener = targetFragment as EditTimeLimitRuleDialogFragmentListener
val database = DefaultAppLogic.with(context!!).database
val database = DefaultAppLogic.with(requireContext()).database
view = FragmentEditTimeLimitRuleDialogBinding.inflate(layoutInflater, container, false)
@ -162,7 +147,8 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment(), DurationPic
startMinuteOfDay = TimeLimitRule.MIN_START_MINUTE,
endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
sessionDurationMilliseconds = 0,
perDay = true
)
} else {
view.isNewRule = false
@ -278,7 +264,8 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment(), DurationPic
start = newRule.startMinuteOfDay,
end = newRule.endMinuteOfDay,
sessionDurationMilliseconds = newRule.sessionDurationMilliseconds,
sessionPauseMilliseconds = newRule.sessionPauseMilliseconds
sessionPauseMilliseconds = newRule.sessionPauseMilliseconds,
perDay = newRule.perDay
)
)) {
return
@ -333,6 +320,9 @@ class EditTimeLimitRuleDialogFragment : BottomSheetDialogFragment(), DurationPic
}
}
view.typePerDay.setOnClickListener { newRule = newRule.copy(perDay = true); bindRule() }
view.typePerWeek.setOnClickListener { newRule = newRule.copy(perDay = false); bindRule() }
database.config().getEnableAlternativeDurationSelectionAsync().observe(viewLifecycleOwner, Observer {
view.timeSpan.enablePickerMode(it)
})

View file

@ -42,44 +42,38 @@ class DefaultCategories private constructor(private val context: Context) {
val rules = mutableListOf<TimeLimitRule>()
// maximum time for each workday
for (day in 0..4) {
rules.add(
TimeLimitRule(
id = IdGenerator.generateId(),
categoryId = categoryId,
applyToExtraTimeUsage = false,
dayMask = (1 shl day).toByte(),
maximumTimeInMillis = 1000 * 60 * 30, // 30 minutes
startMinuteOfDay = TimeLimitRule.MIN_START_MINUTE,
endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
)
)
}
rules.add(
TimeLimitRule(
id = IdGenerator.generateId(),
categoryId = categoryId,
applyToExtraTimeUsage = false,
dayMask = (1 or 2 or 4 or 8 or 16).toByte(),
maximumTimeInMillis = 1000 * 60 * 30, // 30 minutes
startMinuteOfDay = TimeLimitRule.MIN_START_MINUTE,
endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0,
perDay = true
)
)
// maximum time for each weekend day
for (day in 5..6) {
rules.add(
TimeLimitRule(
id = IdGenerator.generateId(),
categoryId = categoryId,
applyToExtraTimeUsage = false,
dayMask = (1 shl day).toByte(),
maximumTimeInMillis = 1000 * 60 * 60 * 3, // 3 hours
startMinuteOfDay = TimeLimitRule.MIN_START_MINUTE,
endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
)
)
}
rules.add(
TimeLimitRule(
id = IdGenerator.generateId(),
categoryId = categoryId,
applyToExtraTimeUsage = false,
dayMask = (32 or 64).toByte(),
maximumTimeInMillis = 1000 * 60 * 60 * 3, // 3 hours
startMinuteOfDay = TimeLimitRule.MIN_START_MINUTE,
endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0,
perDay = true
)
)
// maximum time per total week
val dayMask = BitSet()
dayMask.set(0, 7)
rules.add(
TimeLimitRule(
id = IdGenerator.generateId(),
@ -90,7 +84,8 @@ class DefaultCategories private constructor(private val context: Context) {
startMinuteOfDay = TimeLimitRule.MIN_START_MINUTE,
endMinuteOfDay = TimeLimitRule.MAX_END_MINUTE,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
sessionDurationMilliseconds = 0,
perDay = false
)
)
@ -105,7 +100,8 @@ class DefaultCategories private constructor(private val context: Context) {
startMinuteOfDay = 0,
endMinuteOfDay = 6 * 60 - 1,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
sessionDurationMilliseconds = 0,
perDay = false
)
)
@ -119,7 +115,8 @@ class DefaultCategories private constructor(private val context: Context) {
startMinuteOfDay = 18 * 60,
endMinuteOfDay = MinuteOfDay.MAX,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
sessionDurationMilliseconds = 0,
perDay = false
)
)
@ -134,7 +131,8 @@ class DefaultCategories private constructor(private val context: Context) {
startMinuteOfDay = 0,
endMinuteOfDay = 9 * 60 - 1,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
sessionDurationMilliseconds = 0,
perDay = false
)
)
@ -148,7 +146,8 @@ class DefaultCategories private constructor(private val context: Context) {
startMinuteOfDay = 20 * 60,
endMinuteOfDay = MinuteOfDay.MAX,
sessionPauseMilliseconds = 0,
sessionDurationMilliseconds = 0
sessionDurationMilliseconds = 0,
perDay = false
)
)

View file

@ -38,6 +38,10 @@
name="affectsMultipleDays"
type="boolean" />
<variable
name="showWeeklyDailyOption"
type="boolean" />
<variable
name="startTime"
type="String" />
@ -80,6 +84,27 @@
<include layout="@layout/view_select_days" android:id="@+id/day_selection" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:visibility="@{showWeeklyDailyOption ? View.VISIBLE : View.GONE}"
android:id="@+id/per_day_group"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/type_per_day"
style="?materialButtonOutlinedStyle"
android:text="@string/category_time_limit_rules_per_day"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton
android:id="@+id/type_per_week"
style="?materialButtonOutlinedStyle"
android:text="@string/category_time_limit_rules_per_week"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<io.timelimit.android.ui.view.SelectTimeSpanView
android:id="@+id/time_span"
android:layout_width="match_parent"
@ -107,8 +132,9 @@
android:layout_height="wrap_content">
<TextView
android:textAppearance="?android:textAppearanceMedium"
android:padding="8dp"
android:textAppearance="?android:textAppearanceSmall"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:text="@string/category_time_limit_rules_apply_to_part_day_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
@ -122,7 +148,7 @@
android:layout_height="wrap_content" />
<TextView
android:textAppearance="?android:textAppearanceMedium"
android:textAppearance="?android:textAppearanceSmall"
android:padding="8dp"
android:text="@string/category_time_limit_rules_apply_to_part_day_2"
android:layout_width="wrap_content"
@ -161,10 +187,10 @@
app:layout_gravity="fill_vertical|fill_horizontal"
android:gravity="center_vertical"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:paddingStart="0dp"
app:layout_row="1"
app:layout_column="1"
android:textAppearance="?android:textAppearanceMedium"
android:textAppearance="?android:textAppearanceSmall"
android:text="@string/category_time_limit_rules_session_limit_duration"
android:layout_width="0dp"
android:layout_height="wrap_content" />
@ -187,8 +213,8 @@
app:layout_gravity="fill_vertical|fill_horizontal"
android:gravity="center_vertical"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:textAppearance="?android:textAppearanceMedium"
android:paddingStart="0dp"
android:textAppearance="?android:textAppearanceSmall"
app:layout_row="2"
app:layout_column="1"
android:text="@string/category_time_limit_rules_session_limit_pause"
@ -217,13 +243,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:visibility="@{safeUnbox(affectsMultipleDays) ? View.VISIBLE : View.GONE}"
android:textAppearance="?android:textAppearanceSmall"
android:text="@string/category_time_limit_rules_warning_multiple_days"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
@ -257,4 +276,4 @@
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</layout>
</layout>

View file

@ -327,11 +327,9 @@
<string name="category_time_limit_rules_snackbar_created">Regel wurde erstellt</string>
<string name="category_time_limit_rules_snackbar_updated">Regel wurde geändert</string>
<string name="category_time_limit_rules_snackbar_deleted">Regel wurde gelöscht</string>
<string name="category_time_limit_rules_per_day">täglich</string>
<string name="category_time_limit_rules_per_week">wöchentlich</string>
<string name="category_time_limit_rules_warning_multiple_days">Diese Regel
wird die Gesamtnutzungsdauer in einer Woche an den gewählten Tagen einschränken.
Wenn Sei die Begrenzung je Tag wollen, dann erstellen Sie eine Regel je Tag.
</string>
<string name="category_time_limit_rules_warning_day_part">Diese Regel begrenzt nur
die Nutzung im angegebenen Intervall. Ohne Regeln für andere Intervalle
oder Sperrzeiten wird es keine Begrenzung außerhalb dieses Intervalls
@ -1034,14 +1032,6 @@
\n2. Wenn \"es nicht funktioniert\" liegt es oft an Energiesparfunktionen die deaktiviert werden müssen.
Diese sind oft automatisch aktiviert, ohne das es Ihnen als Nutzer bewusst ist.
</string>
<string name="must_read_timelimit_rules">
Da es manchmal falsch bedient wird:
Eine Regel begrenzt die Gesamtnutzungsdauer der Kategorie in einer Woche an den gewählten Tagen.
Das bedeutet, dass wenn eine Regel für Montag bis Freitag mit 3 Stunden eingestellt wird,
die Kategorie in jeder Woche von Montag bis Freitag insgesamt nur 3 Stunden benutzt werden kann.
Wenn je eine Regel für die Tage von Montag bis Freitag mit jeweils 3 Stunden gewählt wird,
kann die Kategorie an jedem dieser Tage für 3 Stunden genutzt werden.
</string>
<string name="must_read_add_already_assigned_apps">
Apps mit Kategorie werden beim Hinzufügen aus der vorherigen Kategorie entfernt - für mehrere Kategorien gleichzeitig können Sie Ober- und Unter-Kategorien verwenden
</string>

View file

@ -962,14 +962,6 @@ Esta %s só é permitida em algumas redes, mas nenhuma conexão com tal rede foi
\n2nd: Se \"não funciona\" então isto é muito provavelmente causado por recursos de economia da bateria.
Estos recursos são freqüentemente ativadas mesmo que você não o tenha feito.
</string>
<string name="must_read_timelimit_rules">
Isso acontece às vezes porque é configurado de forma errada :
Uma regra limita a duração total de uso de uma categoria nos dias selecionados da semana.
Se houver uma regra de segunda-feira a sexta-feira com 3 horas,
então há um limite total de 3 horas por semana, de segunda a sexta.
Se houver uma regra por dia com 3 horas,
então há um limite de 3 horas por dia.
</string>
<string name="must_read_add_already_assigned_apps">
Os aplicativos com categoria são excluídos da categoria anterior quando são adicionados a uma nova categoria -
usar categorias de Pais e filhos para \"adicionar\" um aplicativo a múltiplas categorias

View file

@ -367,15 +367,13 @@
<string name="category_time_limit_rules_enable_session_limit">Limit session duration</string>
<string name="category_time_limit_rules_session_limit_duration">Maximum session duration</string>
<string name="category_time_limit_rules_session_limit_pause">Break duration after session</string>
<string name="category_time_limit_rules_per_day">daily</string>
<string name="category_time_limit_rules_per_week">weekly</string>
<string name="category_time_limit_rules_snackbar_created">Rule was created</string>
<string name="category_time_limit_rules_snackbar_updated">Rule was modified</string>
<string name="category_time_limit_rules_snackbar_deleted">Rule was deleted</string>
<string name="category_time_limit_rules_warning_multiple_days">This rule
will limit the total usage duration during one week at the selected days.
If you want it per day, create one rule per day.
</string>
<string name="category_time_limit_rules_warning_day_part">This rule
will limit the usage duration only in the specified interval.
Without rules for other times or blocked time areas, there will
@ -1077,14 +1075,6 @@
\n2nd: If \"it does not work\" then this is most likely caused by power saving features.
This features are often enabled even if you did not enable it actively.
</string>
<string name="must_read_timelimit_rules">
Because it happens sometimes that it is configured wrongly:
A rule limits the total usage duration of a category at the selected days of the week.
If there is a rule from monday to friday with 3 hours,
then there is a limit of total 3 hours per week from monday to friday.
If there is one rule per day with 3 hours,
then there is a limit of 3 hours per day.
</string>
<string name="must_read_add_already_assigned_apps">
Apps with category are removed from the previous category when they are added to a new category -
use parent and child categories to \"add\" an App to multiple categories