Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32bf2d0992 |
@@ -15,8 +15,8 @@ android {
|
|||||||
applicationId = "today.notfeelingit.lcdpet"
|
applicationId = "today.notfeelingit.lcdpet"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 2
|
versionCode = 3
|
||||||
versionName = "1.1"
|
versionName = "1.2"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation("androidx.datastore:datastore-preferences:1.2.1")
|
implementation("androidx.datastore:datastore-preferences:1.2.1")
|
||||||
|
implementation("androidx.work:work-runtime-ktx:2.11.2")
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
|||||||
@@ -39,3 +39,45 @@ App file: tv_off.ogg
|
|||||||
License: Creative Commons 0 / CC0
|
License: Creative Commons 0 / CC0
|
||||||
Use: Watch TV CRT power-off sound
|
Use: Watch TV CRT power-off sound
|
||||||
Edited for length
|
Edited for length
|
||||||
|
|
||||||
|
|
||||||
|
Pet excited / activity sound:
|
||||||
|
“Formula 1 radio” by Fenrir21
|
||||||
|
Source: Freesound
|
||||||
|
Sound page: https://freesound.org/people/Fenrir21/sounds/825219/
|
||||||
|
Original sound ID: 825219
|
||||||
|
App file: pet_excited.ogg
|
||||||
|
License: Creative Commons 0 / CC0
|
||||||
|
Use: PLAY and WALK activity one-shot
|
||||||
|
Converted and edited for app use
|
||||||
|
|
||||||
|
Pet eating sound:
|
||||||
|
“Beep Beep.wav” by TubbsMedia
|
||||||
|
Source: Freesound
|
||||||
|
Sound page: https://freesound.org/people/TubbsMedia/sounds/475981/
|
||||||
|
Original sound ID: 475981
|
||||||
|
App file: pet_eating.ogg
|
||||||
|
License: Creative Commons 0 / CC0
|
||||||
|
Use: Snack and full-meal eating one-shot
|
||||||
|
Converted and edited for app use
|
||||||
|
|
||||||
|
Pet becoming-sick sound:
|
||||||
|
“random-beeps” by SkySpeira
|
||||||
|
Source: Freesound
|
||||||
|
Sound page: https://freesound.org/people/SkySpeira/sounds/716508/
|
||||||
|
Original sound ID: 716508
|
||||||
|
App file: pet_sick.ogg
|
||||||
|
License: Creative Commons 0 / CC0
|
||||||
|
Use: One-shot when the pet transitions into Sick
|
||||||
|
Converted and edited for app use
|
||||||
|
|
||||||
|
Urgent Need notification sound:
|
||||||
|
“Train Door Beep, A.wav” by InspectorJ
|
||||||
|
Source: Freesound
|
||||||
|
Sound page: https://freesound.org/people/InspectorJ/sounds/346573/
|
||||||
|
Creator website: https://www.jshaw.co.uk/
|
||||||
|
Original sound ID: 346573
|
||||||
|
App file: need_urgent.ogg
|
||||||
|
License: Creative Commons Attribution 4.0
|
||||||
|
Use: Urgent Hunger, Potty, Dirtiness, and Energy notifications
|
||||||
|
Converted and edited for app use
|
||||||
|
|||||||
+689
-96
@@ -1,223 +1,816 @@
|
|||||||
LCDPET HOW TO
|
LCDPET HOW TO
|
||||||
|
|
||||||
THE GOAL
|
STARTING A NEW PET
|
||||||
|
|
||||||
LCDPet is a virtual pet that changes over time while you care for it.
|
When LCDPet starts without an active pet, the CHOOSE NEW PET screen appears.
|
||||||
|
|
||||||
Your goal is to build the strongest bond possible while keeping your pet healthy, happy, rested, clean, and cared for.
|
Choose one of these pet templates:
|
||||||
|
|
||||||
Pay attention to your pet's needs, respond at the right time, and avoid relying on the same action again and again.
|
Classic
|
||||||
|
Scruffy
|
||||||
|
Round Bean
|
||||||
|
Pocket Pup
|
||||||
|
Batty
|
||||||
|
Goblin
|
||||||
|
|
||||||
Regular care matters. Your relationship with the pet grows through the choices you make over time.
|
Tap a preview to select it.
|
||||||
|
|
||||||
CARING FOR YOUR PET
|
Enter a name under NAME PET.
|
||||||
|
|
||||||
Watch the status bars and messages to see how your pet is doing.
|
Names:
|
||||||
|
|
||||||
When your pet needs something, choose the action that fits the situation.
|
Must contain letters only
|
||||||
|
Can be up to 12 letters long
|
||||||
|
Cannot use the reserved names Patch or Petch
|
||||||
|
|
||||||
Good care improves the Care record and helps Bond grow.
|
KEEP NAME remains unavailable until the name is valid.
|
||||||
|
|
||||||
|
Tap KEEP NAME to begin caring for the pet.
|
||||||
|
|
||||||
|
|
||||||
|
MAIN SCREEN
|
||||||
|
|
||||||
|
The pet's name and the Stats control appear at the upper left.
|
||||||
|
|
||||||
|
The current location and weather appear at the upper right.
|
||||||
|
|
||||||
|
Tap the location and weather line to move between Inside and Outside.
|
||||||
|
|
||||||
|
Credits / Licenses appears below the location and weather line.
|
||||||
|
|
||||||
|
The health hearts appear above the pet.
|
||||||
|
|
||||||
|
The line below the pet shows the most urgent current alert, an active Want, or the pet's normal status.
|
||||||
|
|
||||||
|
Only one message is shown at a time. An urgent warning can temporarily hide a Want without cancelling it.
|
||||||
|
|
||||||
|
The six status bars appear below the pet:
|
||||||
|
|
||||||
|
Happiness
|
||||||
|
Energy
|
||||||
|
Hunger
|
||||||
|
Dirtiness
|
||||||
|
Potty
|
||||||
|
Boredom
|
||||||
|
|
||||||
|
Filled blocks show the current value.
|
||||||
|
|
||||||
|
For Happiness and Energy, a higher value is generally better.
|
||||||
|
|
||||||
|
For Hunger, Dirtiness, Potty, and Boredom, a higher value means the pet needs more attention.
|
||||||
|
|
||||||
|
|
||||||
|
MENUS AND UNAVAILABLE ACTIONS
|
||||||
|
|
||||||
|
Inside contains the CARE and SOCIALIZE menus.
|
||||||
|
|
||||||
|
Outside contains WALK and POTTY.
|
||||||
|
|
||||||
|
Opening Stats, Care, Socialize, How To, or Credits / Licenses closes other major menus as needed.
|
||||||
|
|
||||||
|
Actions are temporarily unavailable while the pet is sleeping, bathing, being traded, talking, or completing another action.
|
||||||
|
|
||||||
|
An unavailable button appears faded.
|
||||||
|
|
||||||
|
How To appears near the bottom of the screen while the Care and Socialize menus are closed.
|
||||||
|
|
||||||
Missed needs, indoor accidents, overfeeding, sickness, and other harmful situations can increase Neglect and leave your pet in worse condition.
|
|
||||||
|
|
||||||
INSIDE AND OUTSIDE
|
INSIDE AND OUTSIDE
|
||||||
|
|
||||||
Tap the Inside / Outside line near the top of the screen to change locations.
|
Tap the Inside / Outside and weather line near the top of the screen to change location.
|
||||||
|
|
||||||
Inside is where most care and social actions happen.
|
Most care and social actions happen Inside.
|
||||||
|
|
||||||
Outside is where your pet can walk and safely use the bathroom.
|
Walk and Potty are available Outside.
|
||||||
|
|
||||||
|
The location cannot be changed while another action is in progress.
|
||||||
|
|
||||||
|
A Weak or RecoverWeak pet cannot be taken Outside.
|
||||||
|
|
||||||
|
Going Outside normally increases Dirtiness by 1 and fills Happiness.
|
||||||
|
|
||||||
|
If the weather is Rain, going Outside normally fills Dirtiness to 10, sets Happiness to 8, and makes the pet Nervous.
|
||||||
|
|
||||||
|
Completing an Outside Want avoids the normal Rain penalty and fills Happiness.
|
||||||
|
|
||||||
|
Returning Inside sets the weather display to Sunny and usually returns a healthy pet to Calm.
|
||||||
|
|
||||||
|
Leaving a pet Outside while the app is closed removes 1 Energy for each hour away.
|
||||||
|
|
||||||
|
If enough Energy is lost, the pet falls asleep Outside and receives the outdoor Rough Wake penalties when the app is reopened.
|
||||||
|
|
||||||
|
|
||||||
|
WEATHER
|
||||||
|
|
||||||
|
A new pet can begin with Sunny, Cloudy, Rain, or Snow weather.
|
||||||
|
|
||||||
|
Weather changes automatically after a random 4, 6, 9, or 12 hours.
|
||||||
|
|
||||||
|
The normal weather cycle is:
|
||||||
|
|
||||||
|
Sunny changes to Cloudy.
|
||||||
|
Cloudy changes to Sunny or Rain.
|
||||||
|
Rain changes to Cloudy.
|
||||||
|
Snow changes to Sunny.
|
||||||
|
|
||||||
|
Weather can change while the pet is Inside.
|
||||||
|
|
||||||
|
In good outdoor weather, the pet's mood depends on Bond:
|
||||||
|
|
||||||
|
Bond below 40: Nervous
|
||||||
|
Bond 40 through 89: Calm
|
||||||
|
Bond 90 or higher: Happy
|
||||||
|
|
||||||
|
Rain makes the pet Nervous until Bond reaches 100, unless the outing completed an Outside Want.
|
||||||
|
|
||||||
|
Remaining Outside in Rain lowers Happiness by 1 each hour unless the outing completed an Outside Want.
|
||||||
|
|
||||||
Some situations are safe outside but can cause trouble indoors, so location matters.
|
|
||||||
|
|
||||||
CARE
|
CARE
|
||||||
|
|
||||||
|
Open CARE while Inside to find BATHE, FEED, and NAP.
|
||||||
|
|
||||||
|
|
||||||
BATHE
|
BATHE
|
||||||
|
|
||||||
Bathing reduces Dirtiness and removes stains left by indoor accidents.
|
BATHE is available when Dirtiness is above zero or the pet currently Wants a Bath.
|
||||||
|
|
||||||
A pet with a stronger Bond accepts bathing more easily and may become fully clean.
|
Bathing removes an indoor accident stain.
|
||||||
|
|
||||||
|
Bath effectiveness depends on Bond:
|
||||||
|
|
||||||
|
Bond below 30:
|
||||||
|
The bath lasts about 2 minutes.
|
||||||
|
Dirtiness is reduced by 3.
|
||||||
|
Happiness is reduced by 1.
|
||||||
|
|
||||||
|
Bond 30 through 59:
|
||||||
|
The bath lasts about 4 minutes.
|
||||||
|
Dirtiness is reduced by 6.
|
||||||
|
|
||||||
|
Bond 60 through 89:
|
||||||
|
The bath lasts about 6 minutes.
|
||||||
|
Dirtiness is completely removed.
|
||||||
|
|
||||||
|
Bond 90 or higher:
|
||||||
|
The bath lasts about 6 minutes.
|
||||||
|
Dirtiness is completely removed.
|
||||||
|
Happiness increases by 1.
|
||||||
|
|
||||||
|
A Bath Want gives a special result:
|
||||||
|
|
||||||
|
Dirtiness becomes 0.
|
||||||
|
Happiness becomes 10.
|
||||||
|
Energy increases by 2.
|
||||||
|
Boredom becomes 0.
|
||||||
|
|
||||||
|
A bath adds 1 Care when it reduces Dirtiness.
|
||||||
|
|
||||||
|
Bathing a pet made Sick by maximum Dirtiness begins recovery once Dirtiness drops below 10.
|
||||||
|
|
||||||
Bathing also helps with recovery when extreme Dirtiness caused sickness.
|
|
||||||
|
|
||||||
FEED
|
FEED
|
||||||
|
|
||||||
Feeding lowers the Hunger value.
|
A high Hunger value means the pet needs food.
|
||||||
|
|
||||||
A high Hunger value means your pet needs food.
|
A low Hunger value means the pet is already full.
|
||||||
|
|
||||||
A very low Hunger value means your pet is already full.
|
FEED lowers Hunger by 1.
|
||||||
|
|
||||||
|
Every third successful meal increases Potty by 1.
|
||||||
|
|
||||||
|
Every second successful meal increases Happiness by 1.
|
||||||
|
|
||||||
|
A successful meal adds 1 Care and may build Bond.
|
||||||
|
|
||||||
|
Feeding while Hunger is already at 0 counts as overfeeding.
|
||||||
|
|
||||||
|
Three overfeeding attempts add 1 Neglect, remove Health, and can make the pet Sick or Weak.
|
||||||
|
|
||||||
|
FEED is unavailable while a pet is Sick, Weak, or recovering specifically because of overfeeding.
|
||||||
|
|
||||||
|
A Snack Want is a small treat rather than a full meal.
|
||||||
|
|
||||||
|
Completing a Snack Want:
|
||||||
|
|
||||||
|
Increases Happiness by 1.
|
||||||
|
Increases Energy by 1.
|
||||||
|
Adds 1 Care.
|
||||||
|
Can build Bond immediately.
|
||||||
|
Does not change Hunger.
|
||||||
|
Does not increase Potty.
|
||||||
|
Does not count as overfeeding.
|
||||||
|
|
||||||
Feeding an already full pet too many times can cause sickness from overfeeding, so pay attention to Hunger before feeding.
|
|
||||||
|
|
||||||
NAP
|
NAP
|
||||||
|
|
||||||
Napping restores Energy and helps your pet recover.
|
NAP is available while Inside whenever Energy is below 10.
|
||||||
|
|
||||||
If Energy reaches zero, your pet may eventually fall asleep on its own.
|
A manual Nap lasts a random 2, 3, 4, or 5 hours.
|
||||||
|
|
||||||
|
During the Nap, Health, Happiness, and Energy gradually rise.
|
||||||
|
|
||||||
|
When the Nap finishes:
|
||||||
|
|
||||||
|
Health becomes 10.
|
||||||
|
Happiness becomes 10.
|
||||||
|
Energy becomes 10.
|
||||||
|
|
||||||
|
If Hunger was below 5, it rises to 5.
|
||||||
|
|
||||||
|
If Potty was below 7, it rises to 7.
|
||||||
|
|
||||||
|
The pet may therefore need food and the bathroom after waking.
|
||||||
|
|
||||||
|
A completed Nap can clear Sick, Weak, Recovering, and RecoverWeak conditions.
|
||||||
|
|
||||||
Choosing an indoor nap is safer and calmer than letting your pet collapse from exhaustion.
|
|
||||||
|
|
||||||
SOCIALIZE
|
SOCIALIZE
|
||||||
|
|
||||||
|
Open SOCIALIZE while Inside to find PET, PLAY, and TALK.
|
||||||
|
|
||||||
|
Repeating the same social action too many times can raise Boredom.
|
||||||
|
|
||||||
|
Changing to another social action lowers Boredom by 1.
|
||||||
|
|
||||||
|
|
||||||
PET
|
PET
|
||||||
|
|
||||||
Petting improves Happiness and can help Bond grow.
|
PET is available Inside while no other action is running.
|
||||||
|
|
||||||
|
Petting:
|
||||||
|
|
||||||
|
Increases Happiness by 1.
|
||||||
|
Adds 1 Care.
|
||||||
|
May build Bond.
|
||||||
|
|
||||||
|
Completing a Pet Want gives twice the normal Bond gain and bypasses the normal Bond cooldown.
|
||||||
|
|
||||||
|
|
||||||
PLAY
|
PLAY
|
||||||
|
|
||||||
Playing improves Happiness.
|
PLAY requires at least 1 Energy.
|
||||||
|
|
||||||
It can also use Energy and increase Hunger, so check the status bars afterward.
|
PLAY is unavailable while the pet is Weak or RecoverWeak.
|
||||||
|
|
||||||
|
Ordinary Play:
|
||||||
|
|
||||||
|
Increases Happiness by 1.
|
||||||
|
Increases Hunger by 1.
|
||||||
|
Adds 1 Care.
|
||||||
|
|
||||||
|
Every second ordinary Play lowers Energy by 1.
|
||||||
|
|
||||||
|
Completing a Play Want:
|
||||||
|
|
||||||
|
Increases Happiness by 2.
|
||||||
|
Adds 1 Care.
|
||||||
|
Does not increase Hunger.
|
||||||
|
Does not lower Energy.
|
||||||
|
Does not build Boredom.
|
||||||
|
Gives twice the normal Bond gain immediately.
|
||||||
|
|
||||||
Using Play repeatedly can eventually increase Boredom instead of helping.
|
|
||||||
|
|
||||||
TALK
|
TALK
|
||||||
|
|
||||||
Press and hold TALK while speaking or making sounds.
|
Press and hold TALK while speaking or making sounds.
|
||||||
|
|
||||||
The microphone reacts only to how loud the sound is.
|
Release TALK to finish the action.
|
||||||
|
|
||||||
LCDPet does not recognize words, create transcripts, save recordings, or upload audio.
|
The microphone graphic changes according to the live sound level.
|
||||||
|
|
||||||
Release TALK to finish.
|
TALK does not recognize words, create transcripts, save recordings, or upload audio.
|
||||||
|
|
||||||
|
The first press may request microphone permission.
|
||||||
|
|
||||||
|
After granting permission, release the button and hold TALK again to begin the action.
|
||||||
|
|
||||||
|
If microphone permission is denied, TALK cannot be completed until permission is granted.
|
||||||
|
|
||||||
|
Completing an ordinary Talk:
|
||||||
|
|
||||||
|
Increases Happiness by 1.
|
||||||
|
Adds 1 Care.
|
||||||
|
May build Bond.
|
||||||
|
|
||||||
|
Completing a Talk Want gives five times the normal Bond gain and bypasses the normal Bond cooldown.
|
||||||
|
|
||||||
|
|
||||||
|
OUTSIDE ACTIONS
|
||||||
|
|
||||||
|
Move the pet Outside to find WALK and POTTY.
|
||||||
|
|
||||||
OUTSIDE
|
|
||||||
|
|
||||||
WALK
|
WALK
|
||||||
|
|
||||||
Walking restores Health.
|
WALK requires at least 1 Energy.
|
||||||
|
|
||||||
It also uses Energy and can increase Hunger and Dirtiness.
|
WALK is unavailable while the pet is Sick, Weak, Recovering, or RecoverWeak.
|
||||||
|
|
||||||
|
Walking:
|
||||||
|
|
||||||
|
Restores Health to 10.
|
||||||
|
Lowers Energy by 1.
|
||||||
|
Increases Hunger by 1.
|
||||||
|
Increases Dirtiness by 1.
|
||||||
|
Adds 1 Care.
|
||||||
|
|
||||||
A pet that is exhausted, Weak, or Sick may not be able to walk.
|
|
||||||
|
|
||||||
POTTY
|
POTTY
|
||||||
|
|
||||||
The Potty value increases over time and after meals.
|
POTTY is available Outside when the Potty value is above zero.
|
||||||
|
|
||||||
Use Potty while outside to safely reset it.
|
Using POTTY:
|
||||||
|
|
||||||
If the value reaches its limit indoors, your pet can have an accident.
|
Resets Potty to 0.
|
||||||
|
Increases Hunger by 1.
|
||||||
|
Adds 1 Care.
|
||||||
|
Records the Potty Need as completed.
|
||||||
|
|
||||||
An indoor accident leaves a stain, increases Dirtiness, and adds Neglect.
|
If Potty is already full while the pet remains Outside, the pet handles it automatically.
|
||||||
|
|
||||||
|
This resets Potty to 0 but removes 1 Health.
|
||||||
|
|
||||||
|
|
||||||
|
NEEDS
|
||||||
|
|
||||||
|
Needs are ongoing care conditions shown by the status bars and warning messages.
|
||||||
|
|
||||||
|
Meeting a Need can add Care and create a completed entry in the Care Journal.
|
||||||
|
|
||||||
|
Ignoring a serious Need can add Neglect, remove Health, and cause sickness.
|
||||||
|
|
||||||
|
|
||||||
|
HUNGER NEED
|
||||||
|
|
||||||
|
Hunger normally increases by 1 every 2 hours.
|
||||||
|
|
||||||
|
When Hunger reaches 10, the pet is starving.
|
||||||
|
|
||||||
|
After enough additional missed hunger checks:
|
||||||
|
|
||||||
|
The Care Journal records a missed Hunger Need.
|
||||||
|
Neglect increases by 1.
|
||||||
|
Health decreases by 1.
|
||||||
|
The pet becomes Sick, or Weak if Health reaches 0.
|
||||||
|
|
||||||
|
Feed the pet before Hunger remains full for too long.
|
||||||
|
|
||||||
|
|
||||||
|
POTTY NEED
|
||||||
|
|
||||||
|
Potty normally increases by 1 every 3 hours.
|
||||||
|
|
||||||
|
The app warns the player when Potty reaches 9 while the pet is Inside.
|
||||||
|
|
||||||
|
If Potty is already full when the next Potty increase happens Inside:
|
||||||
|
|
||||||
|
The pet has an accident.
|
||||||
|
The Care Journal records a missed Potty Need.
|
||||||
|
Neglect increases by 1.
|
||||||
|
Potty resets to 0.
|
||||||
|
Dirtiness increases by 3.
|
||||||
|
An accident stain appears.
|
||||||
|
|
||||||
|
Bathing removes the stain.
|
||||||
|
|
||||||
|
|
||||||
|
DIRTINESS NEED
|
||||||
|
|
||||||
|
Dirtiness can increase from outdoor trips, walking, Rain, and indoor accidents.
|
||||||
|
|
||||||
|
The app warns the player when Dirtiness reaches 9.
|
||||||
|
|
||||||
|
If Dirtiness remains at 10 for about 12 hours:
|
||||||
|
|
||||||
|
The Care Journal records a missed Bath Need.
|
||||||
|
Neglect increases by 1.
|
||||||
|
Health decreases by 1.
|
||||||
|
The pet becomes Sick, or Weak if Health reaches 0.
|
||||||
|
|
||||||
|
Bathe the pet to correct sickness caused by Dirtiness.
|
||||||
|
|
||||||
|
|
||||||
|
ENERGY NEED
|
||||||
|
|
||||||
|
The app warns the player when Energy reaches 1.
|
||||||
|
|
||||||
|
Some actions become unavailable when Energy is too low.
|
||||||
|
|
||||||
|
If Energy remains at 0 for 30 minutes while no other action is running, the pet falls asleep automatically.
|
||||||
|
|
||||||
Take your pet outside before the Potty value reaches its limit.
|
|
||||||
|
|
||||||
WANTS
|
WANTS
|
||||||
|
|
||||||
Your pet may occasionally ask for something specific.
|
A Want is a temporary request from the pet.
|
||||||
|
|
||||||
A Want can ask for Play, Pet, Talk, a Snack, a Bath, or a trip Outside.
|
A new Want appears after a random 8, 12, 18, or 24 hours.
|
||||||
|
|
||||||
Completing the request before it disappears gives extra Bond.
|
Each Want lasts 10 minutes.
|
||||||
|
|
||||||
An ignored Want eventually expires, but it does not immediately harm your pet.
|
When a Want appears, the app can send a notification.
|
||||||
|
|
||||||
|
If the Want is not completed before it expires, the Care Journal records it as missed.
|
||||||
|
|
||||||
|
Missing a Want does not add Neglect.
|
||||||
|
|
||||||
|
Possible Wants while Inside:
|
||||||
|
|
||||||
|
Snack
|
||||||
|
Play
|
||||||
|
Pet
|
||||||
|
Talk
|
||||||
|
Outside
|
||||||
|
|
||||||
|
Bath joins the indoor Want list at 100 Bond.
|
||||||
|
|
||||||
|
Possible Wants while Outside:
|
||||||
|
|
||||||
|
Play
|
||||||
|
Pet
|
||||||
|
Talk
|
||||||
|
|
||||||
|
Those actions are performed Inside, so bring the pet back Inside to complete them.
|
||||||
|
|
||||||
|
Completed Wants give special rewards.
|
||||||
|
|
||||||
|
Snack:
|
||||||
|
A small treat that increases Happiness and Energy without affecting Hunger or Potty.
|
||||||
|
|
||||||
|
Play:
|
||||||
|
Avoids the usual Hunger and Energy costs and gives twice the normal Bond gain.
|
||||||
|
|
||||||
|
Pet:
|
||||||
|
Gives twice the normal Bond gain.
|
||||||
|
|
||||||
|
Talk:
|
||||||
|
Gives five times the normal Bond gain.
|
||||||
|
|
||||||
|
Outside:
|
||||||
|
Fills Happiness, avoids the normal Rain entry penalty, and builds Bond immediately.
|
||||||
|
|
||||||
|
Bath:
|
||||||
|
Completely cleans the pet, fills Happiness, adds Energy, and clears Boredom.
|
||||||
|
|
||||||
A Snack is a small treat and does not affect Hunger like a full meal.
|
|
||||||
|
|
||||||
BOND
|
BOND
|
||||||
|
|
||||||
Bond shows the strength of your relationship with your pet.
|
Bond measures the relationship between the player and the pet.
|
||||||
|
|
||||||
It grows through steady care, meeting Wants, and responding to needs.
|
Bond ranges from 0 to 100.
|
||||||
|
|
||||||
Some Wants give more Bond than ordinary actions.
|
The relationship names are:
|
||||||
|
|
||||||
Using one action too many times can slow or reverse your progress.
|
0 through 24: New Pet
|
||||||
|
25 through 49: Settled
|
||||||
|
50 through 79: Pal
|
||||||
|
80 through 99: Best Friend
|
||||||
|
100: BFF
|
||||||
|
|
||||||
The strongest Bond comes from understanding your pet and giving it balanced care.
|
Ordinary Bond growth can happen once every 6 hours.
|
||||||
|
|
||||||
STATUS BARS
|
Some completed Wants bypass that cooldown.
|
||||||
|
|
||||||
HAPPINESS
|
The normal Bond gain increases as Bond rises:
|
||||||
|
|
||||||
Shows your pet's current mood.
|
Bond 0 through 20: 2 points
|
||||||
|
Bond 21 through 50: 3 points
|
||||||
|
Bond 51 through 80: 5 points
|
||||||
|
Bond 81 through 99: 7 points
|
||||||
|
|
||||||
ENERGY
|
Some Wants multiply that normal gain.
|
||||||
|
|
||||||
Used by active actions such as Play and Walk.
|
Bond cannot rise above 100.
|
||||||
|
|
||||||
Low Energy means your pet needs rest.
|
Repeating one social action 25 times in a row removes 5 Bond once during that streak.
|
||||||
|
|
||||||
HUNGER
|
Changing actions resets that streak.
|
||||||
|
|
||||||
A high value means your pet needs food.
|
|
||||||
|
|
||||||
A low value means your pet is full.
|
|
||||||
|
|
||||||
DIRTINESS
|
|
||||||
|
|
||||||
Can increase through walking, outdoor time, rain, and accidents.
|
|
||||||
|
|
||||||
High Dirtiness creates a need for a bath and can eventually cause sickness.
|
|
||||||
|
|
||||||
POTTY
|
|
||||||
|
|
||||||
Increases over time and after meals.
|
|
||||||
|
|
||||||
Use Potty outdoors before an indoor accident happens.
|
|
||||||
|
|
||||||
BOREDOM
|
BOREDOM
|
||||||
|
|
||||||
Can increase when the same action is used too many times.
|
Boredom can increase when Play, Pet, or Talk is repeated too many times.
|
||||||
|
|
||||||
Change activities and pay attention to what your pet needs.
|
The first Boredom increase can happen after a random 3 through 13 repeats.
|
||||||
|
|
||||||
HEALTH, SICKNESS, AND RECOVERY
|
Further increases happen at additional random intervals if the same action continues.
|
||||||
|
|
||||||
Ignoring serious needs can make your pet Sick or Weak.
|
As Boredom rises, repeated actions remove Happiness and Energy more often.
|
||||||
|
|
||||||
Possible causes include starvation, overfeeding, and extreme Dirtiness.
|
At Boredom 9 or 10, each triggered penalty removes 2 Happiness and 2 Energy.
|
||||||
|
|
||||||
Messages near the pet tell you what is wrong.
|
Changing to another social action lowers Boredom by 1 and reduces the accumulated penalty.
|
||||||
|
|
||||||
Correct the cause before expecting the pet to recover.
|
|
||||||
|
|
||||||
Feed a starving pet.
|
HEALTH AND STATUS
|
||||||
|
|
||||||
Do not continue feeding an overfed pet.
|
Health is shown by ten hearts above the pet.
|
||||||
|
|
||||||
Bathe a pet that became sick from Dirtiness.
|
Health can be lost through starvation, overfeeding, maximum Dirtiness, or allowing a full Potty meter to overflow Outside.
|
||||||
|
|
||||||
Keep a Weak or recovering pet indoors.
|
The pet's face and status change according to its condition.
|
||||||
|
|
||||||
|
Sick pets appear green.
|
||||||
|
|
||||||
|
|
||||||
|
COMMON STATUSES
|
||||||
|
|
||||||
|
Happy:
|
||||||
|
The pet is in a strong positive mood.
|
||||||
|
|
||||||
|
Calm:
|
||||||
|
The pet is comfortable but not highly excited.
|
||||||
|
|
||||||
|
Nervous:
|
||||||
|
Common for a new or low-Bond pet, during Rain, or after a Rough Wake.
|
||||||
|
|
||||||
|
Sad:
|
||||||
|
Happiness is 3 or lower while Inside.
|
||||||
|
|
||||||
|
Excited:
|
||||||
|
A temporary status during actions such as Play or Walk.
|
||||||
|
|
||||||
|
Asleep:
|
||||||
|
The pet is taking a Nap or has fallen asleep automatically.
|
||||||
|
|
||||||
|
Sick:
|
||||||
|
The pet is losing Health because of a serious care problem.
|
||||||
|
|
||||||
|
Weak:
|
||||||
|
Health has reached 0.
|
||||||
|
|
||||||
|
Recovering:
|
||||||
|
The pet is recovering from ordinary sickness.
|
||||||
|
|
||||||
|
RecoverWeak:
|
||||||
|
The pet is in the second stage of recovery after becoming Weak.
|
||||||
|
|
||||||
|
RoughWake:
|
||||||
|
The temporary warning after automatic sleep.
|
||||||
|
|
||||||
|
Trading:
|
||||||
|
The current pet is being traded for a new pet.
|
||||||
|
|
||||||
|
|
||||||
|
SICKNESS AND RECOVERY
|
||||||
|
|
||||||
|
A Sick pet loses 1 Health every 30 minutes.
|
||||||
|
|
||||||
|
Hunger also continues increasing while the pet is Sick.
|
||||||
|
|
||||||
|
Correct the cause of sickness:
|
||||||
|
|
||||||
|
Starving:
|
||||||
|
Feed the pet.
|
||||||
|
|
||||||
|
Overfed:
|
||||||
|
Stop feeding until Hunger rises above the stuffed range.
|
||||||
|
|
||||||
|
Dirty:
|
||||||
|
Bathe the pet until Dirtiness drops below 10.
|
||||||
|
|
||||||
|
Correcting the cause begins Recovering.
|
||||||
|
|
||||||
|
Recovering is checked about every 10 minutes.
|
||||||
|
|
||||||
|
Recovery takes longer after the pet has remained Sick for more checks.
|
||||||
|
|
||||||
|
Happiness of 8 or higher can shorten recovery.
|
||||||
|
|
||||||
|
A Weak pet must remain Inside with some Energy and Hunger below 10.
|
||||||
|
|
||||||
|
After about 2 hours under those conditions, Weak changes to RecoverWeak and Health becomes 1.
|
||||||
|
|
||||||
|
After about another 2 hours Inside with Hunger below 10, the pet returns to Calm.
|
||||||
|
|
||||||
|
A manual Nap can also clear sickness and weakness when the Nap completes.
|
||||||
|
|
||||||
|
Do not take a Sick, Weak, or recovering pet on a Walk.
|
||||||
|
|
||||||
|
|
||||||
|
AUTOMATIC SLEEP AND ROUGH WAKE
|
||||||
|
|
||||||
|
If Energy remains at 0 for 30 minutes, the pet falls asleep automatically.
|
||||||
|
|
||||||
|
Automatic sleep is saved immediately.
|
||||||
|
|
||||||
|
The pet gains 1 Energy for each hour spent asleep, with at least 1 Energy restored when it wakes.
|
||||||
|
|
||||||
|
Leaving and returning to the app wakes an automatically sleeping pet.
|
||||||
|
|
||||||
|
Automatic waking causes a Rough Wake:
|
||||||
|
|
||||||
|
Happiness decreases by 1.
|
||||||
|
The pet becomes Nervous.
|
||||||
|
A Rough wake alert appears for about 30 seconds.
|
||||||
|
The pet remains Nervous for about another 10 seconds.
|
||||||
|
|
||||||
|
If the pet fell asleep Outside:
|
||||||
|
|
||||||
|
The pet is returned Inside.
|
||||||
|
Bond decreases by 1.
|
||||||
|
The normal Rough Wake penalties also apply.
|
||||||
|
|
||||||
|
A chosen indoor Nap is safer and gives much stronger recovery.
|
||||||
|
|
||||||
|
|
||||||
|
CARE AND NEGLECT
|
||||||
|
|
||||||
|
Care records successful positive actions.
|
||||||
|
|
||||||
|
Care increases by 1 for:
|
||||||
|
|
||||||
|
A successful Feed
|
||||||
|
A completed Snack Want
|
||||||
|
Play
|
||||||
|
Pet
|
||||||
|
Talk
|
||||||
|
Walk
|
||||||
|
Potty
|
||||||
|
A Bath that reduces Dirtiness
|
||||||
|
|
||||||
|
Neglect records serious harmful care failures.
|
||||||
|
|
||||||
|
Neglect increases by 1 for:
|
||||||
|
|
||||||
|
Three overfeeding attempts
|
||||||
|
Starving long enough to become Sick
|
||||||
|
An indoor Potty accident
|
||||||
|
Remaining fully dirty long enough to become Sick
|
||||||
|
|
||||||
|
Care and Neglect each stop increasing at 999.
|
||||||
|
|
||||||
|
A missed Want is recorded in the Care Journal but does not add Neglect.
|
||||||
|
|
||||||
|
|
||||||
|
STATS
|
||||||
|
|
||||||
|
Tap Stats below the pet's name to open the Stats panel.
|
||||||
|
|
||||||
|
Opening Stats closes the Care and Socialize menus.
|
||||||
|
|
||||||
|
Stats contains the Pet Record, Pet Management, and Care Journal.
|
||||||
|
|
||||||
Recovery takes time and may also require rest, food, and safe indoor conditions.
|
|
||||||
|
|
||||||
PET RECORD
|
PET RECORD
|
||||||
|
|
||||||
The Pet Record shows:
|
The Pet Record shows:
|
||||||
|
|
||||||
Bond
|
Bond
|
||||||
The strength of your relationship.
|
The current Bond percentage, Bond bar, and relationship name.
|
||||||
|
|
||||||
Age
|
Age
|
||||||
How long you have cared for your pet.
|
The pet's current age record.
|
||||||
|
|
||||||
Care
|
Care
|
||||||
The positive care you have provided.
|
The number of successful care actions recorded.
|
||||||
|
|
||||||
Neglect
|
Neglect
|
||||||
Missed needs and harmful situations.
|
The number of serious harmful care failures recorded.
|
||||||
|
|
||||||
|
|
||||||
Age means time together. It is not a countdown to death.
|
|
||||||
|
|
||||||
CARE JOURNAL
|
CARE JOURNAL
|
||||||
|
|
||||||
The Care Journal records important events in your pet's life.
|
Open Stats, then tap CARE JOURNAL.
|
||||||
|
|
||||||
It can show completed needs, missed needs, Wants, and other major care moments.
|
The Care Journal stores up to 50 entries.
|
||||||
|
|
||||||
|
The screen shows the eight newest entries matching the selected tab.
|
||||||
|
|
||||||
|
Tabs:
|
||||||
|
|
||||||
|
NEEDS
|
||||||
|
Shows Need entries, the result, and when they happened.
|
||||||
|
|
||||||
|
WANTS
|
||||||
|
Shows Want entries, response time, and when they happened.
|
||||||
|
|
||||||
|
ALL
|
||||||
|
Shows Needs and Wants together.
|
||||||
|
|
||||||
|
Symbols:
|
||||||
|
|
||||||
|
✓ means completed or met.
|
||||||
|
✕ means missed or expired.
|
||||||
|
|
||||||
|
A completed Want can show how quickly the player responded.
|
||||||
|
|
||||||
|
If its original start time is unavailable after reopening the app, the response time may show N/A or a dash.
|
||||||
|
|
||||||
|
|
||||||
|
PET MANAGEMENT AND TRADE-IN
|
||||||
|
|
||||||
|
Open Stats and find PET MANAGEMENT.
|
||||||
|
|
||||||
|
Tap TRADE PET IN to begin the trade-in countdown immediately.
|
||||||
|
|
||||||
|
There is no separate confirmation screen.
|
||||||
|
|
||||||
|
The new-pet search begins after 2 hours.
|
||||||
|
|
||||||
|
A ten-block progress bar and remaining time appear during the wait.
|
||||||
|
|
||||||
|
Tap CHANGE MIND at any time during the countdown to cancel the trade-in.
|
||||||
|
|
||||||
|
Closing the app does not cancel the countdown.
|
||||||
|
|
||||||
|
When the countdown reaches zero, CHANGE MIND is no longer available.
|
||||||
|
|
||||||
|
The screen shows FINDING PET for about 5 seconds.
|
||||||
|
|
||||||
|
The current pet is then replaced.
|
||||||
|
|
||||||
|
Trading resets:
|
||||||
|
|
||||||
|
Pet name and template
|
||||||
|
Bond
|
||||||
|
Care
|
||||||
|
Neglect
|
||||||
|
Status bars
|
||||||
|
Health status
|
||||||
|
Care Journal
|
||||||
|
Active Wants
|
||||||
|
Sleep state
|
||||||
|
Location
|
||||||
|
Other current-pet progress
|
||||||
|
|
||||||
|
The CHOOSE NEW PET screen then appears so a new template and name can be selected.
|
||||||
|
|
||||||
|
|
||||||
|
NOTIFICATIONS
|
||||||
|
|
||||||
|
On Android 13 or newer, LCDPet requests notification permission when the app opens.
|
||||||
|
|
||||||
|
Denying notification permission does not stop gameplay.
|
||||||
|
|
||||||
|
It only prevents LCDPet notifications from appearing.
|
||||||
|
|
||||||
|
Notifications can be sent for:
|
||||||
|
|
||||||
|
New Wants
|
||||||
|
Critical Hunger
|
||||||
|
Potty reaching 9 Inside
|
||||||
|
Dirtiness reaching 9
|
||||||
|
Energy reaching 1
|
||||||
|
|
||||||
|
Tapping a notification opens LCDPet.
|
||||||
|
|
||||||
|
|
||||||
|
SAVING AND TIME AWAY
|
||||||
|
|
||||||
|
LCDPet saves the pet's current state on the device.
|
||||||
|
|
||||||
|
Saved information includes:
|
||||||
|
|
||||||
|
Name and template
|
||||||
|
Status bars and Health
|
||||||
|
Bond, Care, and Neglect
|
||||||
|
Location and weather
|
||||||
|
Health condition and sickness cause
|
||||||
|
Accident stain
|
||||||
|
Active Want
|
||||||
|
Trade-in state
|
||||||
|
Sleep state
|
||||||
|
Care Journal
|
||||||
|
|
||||||
|
The trade-in countdown continues using real elapsed time while the app is closed.
|
||||||
|
|
||||||
|
Automatic sleep uses real elapsed time while the app is closed.
|
||||||
|
|
||||||
|
A pet left Outside loses 1 Energy for each hour the app remains closed.
|
||||||
|
|
||||||
|
Most other gameplay timers do not calculate every hour spent away.
|
||||||
|
|
||||||
|
They resume from the saved values when the app is reopened.
|
||||||
|
|
||||||
|
An active Want can remain after reopening, but its ten-minute expiry period begins again and its original response time may be unavailable.
|
||||||
|
|
||||||
|
A manual Nap remains saved, but time spent with the app closed is not counted toward the original Nap duration.
|
||||||
|
|
||||||
Read it to see how your pet has been treated over time.
|
|
||||||
|
|
||||||
CREDITS AND LICENSES
|
CREDITS AND LICENSES
|
||||||
|
|
||||||
Tap Credits / Licenses near the top of the screen to view acknowledgements, sound credits, and license information.
|
Tap Credits / Licenses near the upper-right corner of the main screen.
|
||||||
|
|
||||||
|
This panel contains acknowledgements, sound credits, license information, and source links.
|
||||||
|
|
||||||
|
Web addresses in Credits / Licenses can be tapped to open them.
|
||||||
|
|
||||||
|
|
||||||
HOW TO AND CREDITS CONTROLS
|
HOW TO AND CREDITS CONTROLS
|
||||||
|
|
||||||
CLOSE returns to your pet.
|
How To and Credits / Licenses open as full-screen scrolling panels.
|
||||||
|
|
||||||
MOVE places the MOVE and CLOSE buttons in another corner.
|
CLOSE returns to the pet.
|
||||||
|
|
||||||
When you move the buttons in How To, they also move in Credits / Licenses, and vice versa.
|
MOVE moves the MOVE and CLOSE controls to another corner.
|
||||||
|
|
||||||
|
The controls cycle through:
|
||||||
|
|
||||||
|
Bottom right
|
||||||
|
Bottom left
|
||||||
|
Top left
|
||||||
|
Top right
|
||||||
|
|
||||||
|
Moving the controls in How To also moves them in Credits / Licenses, and vice versa.
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -30,6 +33,8 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
@@ -40,6 +45,8 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
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.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -51,7 +58,15 @@ fun TouchGrassActivityScreen(
|
|||||||
var grassWaveTick by remember { mutableIntStateOf(0) }
|
var grassWaveTick by remember { mutableIntStateOf(0) }
|
||||||
var grassWidthPx by remember { mutableIntStateOf(1) }
|
var grassWidthPx by remember { mutableIntStateOf(1) }
|
||||||
|
|
||||||
val grassColumns = 33
|
val density = LocalDensity.current
|
||||||
|
val grassFontSize = 12.sp
|
||||||
|
val grassLineHeight = 13.sp
|
||||||
|
val estimatedCharacterWidthPx = with(density) {
|
||||||
|
grassFontSize.toPx() * 0.62f
|
||||||
|
}
|
||||||
|
val grassColumns = (
|
||||||
|
grassWidthPx / estimatedCharacterWidthPx.coerceAtLeast(1f)
|
||||||
|
).toInt().coerceIn(16, 33)
|
||||||
|
|
||||||
fun updateGrassFinger(xPosition: Float) {
|
fun updateGrassFinger(xPosition: Float) {
|
||||||
val safeWidth = maxOf(grassWidthPx, 1)
|
val safeWidth = maxOf(grassWidthPx, 1)
|
||||||
@@ -77,13 +92,17 @@ fun TouchGrassActivityScreen(
|
|||||||
columns = grassColumns
|
columns = grassColumns
|
||||||
),
|
),
|
||||||
fontFamily = FontFamily.Monospace,
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = grassFontSize,
|
||||||
|
lineHeight = grassLineHeight,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
softWrap = false,
|
||||||
|
maxLines = 7,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.onGloballyPositioned { coordinates ->
|
.onGloballyPositioned { coordinates ->
|
||||||
grassWidthPx = coordinates.size.width
|
grassWidthPx = coordinates.size.width
|
||||||
}
|
}
|
||||||
.pointerInput(Unit) {
|
.pointerInput(grassColumns) {
|
||||||
detectDragGestures(
|
detectDragGestures(
|
||||||
onDragStart = { offset ->
|
onDragStart = { offset ->
|
||||||
updateGrassFinger(offset.x)
|
updateGrassFinger(offset.x)
|
||||||
@@ -128,19 +147,59 @@ fun WatchTvActivityScreen(
|
|||||||
// 1 = on, 2 = CRT shutting off, 0 = off
|
// 1 = on, 2 = CRT shutting off, 0 = off
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
var appInForeground by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
lifecycleOwner.lifecycle.currentState.isAtLeast(
|
||||||
|
Lifecycle.State.RESUMED
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var weatherChannelPlayer by remember {
|
||||||
|
mutableStateOf<MediaPlayer?>(null)
|
||||||
|
}
|
||||||
|
var staticChannelPlayer by remember {
|
||||||
|
mutableStateOf<MediaPlayer?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val tvTextFontSize = with(LocalDensity.current) { 14.dp.toSp() }
|
||||||
|
val tvTextLineHeight = with(LocalDensity.current) { 18.dp.toSp() }
|
||||||
|
|
||||||
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
|
when (event) {
|
||||||
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
appInForeground = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Lifecycle.Event.ON_PAUSE,
|
||||||
|
Lifecycle.Event.ON_STOP -> {
|
||||||
|
appInForeground = false
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(selectedChannel, tvPowerState) {
|
LaunchedEffect(selectedChannel, tvPowerState) {
|
||||||
tvFrame = 0
|
tvFrame = 0
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
delay(if (tvPowerState == 2) 120L else 450L)
|
delay(if (tvPowerState == 2) 145L else 450L)
|
||||||
tvFrame += 1
|
tvFrame += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(tvPowerState) {
|
LaunchedEffect(tvPowerState) {
|
||||||
if (tvPowerState == 2) {
|
if (tvPowerState == 2) {
|
||||||
delay(420L)
|
delay(580L)
|
||||||
tvPowerState = 0
|
tvPowerState = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,7 +217,12 @@ fun WatchTvActivityScreen(
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
while (powerOffPlayer?.isPlaying == true) {
|
while (
|
||||||
|
powerOffPlayer?.isPlaying == true &&
|
||||||
|
lifecycleOwner.lifecycle.currentState.isAtLeast(
|
||||||
|
Lifecycle.State.RESUMED
|
||||||
|
)
|
||||||
|
) {
|
||||||
delay(50L)
|
delay(50L)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -167,34 +231,91 @@ fun WatchTvActivityScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DisposableEffect(selectedChannel, tvPowerState) {
|
DisposableEffect(tvPowerState) {
|
||||||
var player: MediaPlayer? = null
|
val weatherPlayer =
|
||||||
|
if (tvPowerState == 1) {
|
||||||
if (tvPowerState == 1) {
|
MediaPlayer.create(
|
||||||
val soundResource = when (selectedChannel) {
|
context,
|
||||||
TvChannel.Weather -> R.raw.weather_channel_jazz
|
R.raw.weather_channel_jazz
|
||||||
TvChannel.NoSignal -> R.raw.tv_static_light
|
)?.apply {
|
||||||
|
isLooping = true
|
||||||
|
setVolume(0f, 0f)
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelVolume = when (selectedChannel) {
|
val staticPlayer =
|
||||||
TvChannel.Weather -> 0.75f
|
if (tvPowerState == 1) {
|
||||||
TvChannel.NoSignal -> 0.60f
|
MediaPlayer.create(
|
||||||
|
context,
|
||||||
|
R.raw.tv_static_light
|
||||||
|
)?.apply {
|
||||||
|
isLooping = true
|
||||||
|
setVolume(0f, 0f)
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
player = MediaPlayer.create(context, soundResource)?.apply {
|
weatherChannelPlayer = weatherPlayer
|
||||||
isLooping = true
|
staticChannelPlayer = staticPlayer
|
||||||
setVolume(channelVolume, channelVolume)
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onDispose {
|
onDispose {
|
||||||
player?.stop()
|
if (weatherChannelPlayer === weatherPlayer) {
|
||||||
player?.release()
|
weatherChannelPlayer = null
|
||||||
player = null
|
}
|
||||||
|
if (staticChannelPlayer === staticPlayer) {
|
||||||
|
staticChannelPlayer = null
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf(weatherPlayer, staticPlayer).forEach { player ->
|
||||||
|
runCatching {
|
||||||
|
if (player?.isPlaying == true) {
|
||||||
|
player.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player?.release()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(
|
||||||
|
appInForeground,
|
||||||
|
tvPowerState,
|
||||||
|
selectedChannel,
|
||||||
|
weatherChannelPlayer,
|
||||||
|
staticChannelPlayer
|
||||||
|
) {
|
||||||
|
val canHearTv =
|
||||||
|
appInForeground && tvPowerState == 1
|
||||||
|
|
||||||
|
val weatherVolume =
|
||||||
|
if (canHearTv && selectedChannel == TvChannel.Weather) {
|
||||||
|
0.75f
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
|
||||||
|
val staticVolume =
|
||||||
|
if (canHearTv && selectedChannel == TvChannel.NoSignal) {
|
||||||
|
0.60f
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherChannelPlayer?.setVolume(
|
||||||
|
weatherVolume,
|
||||||
|
weatherVolume
|
||||||
|
)
|
||||||
|
staticChannelPlayer?.setVolume(
|
||||||
|
staticVolume,
|
||||||
|
staticVolume
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -267,25 +388,73 @@ fun WatchTvActivityScreen(
|
|||||||
.padding(horizontal = 10.dp, vertical = 8.dp),
|
.padding(horizontal = 10.dp, vertical = 8.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
if (
|
||||||
text = if (tvPowerState == 1) {
|
tvPowerState == 1 &&
|
||||||
tvPreviewText(
|
selectedChannel == TvChannel.Weather
|
||||||
channel = selectedChannel,
|
) {
|
||||||
frame = tvFrame,
|
val weatherLines = weatherPreviewText(weatherLabel).lines()
|
||||||
weatherLabel = weatherLabel
|
val weatherGraphic = weatherLines.joinToString("\n") {
|
||||||
|
it.take(11)
|
||||||
|
}
|
||||||
|
val weatherDetails = weatherLines.joinToString("\n") {
|
||||||
|
it.drop(15)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = weatherGraphic,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = tvTextFontSize,
|
||||||
|
lineHeight = tvTextLineHeight,
|
||||||
|
color = lcdDark,
|
||||||
|
modifier = Modifier.width(92.dp),
|
||||||
|
softWrap = false,
|
||||||
|
maxLines = 3
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
tvPowerOffPreviewText(
|
Spacer(modifier = Modifier.width(34.dp))
|
||||||
frame = tvFrame,
|
|
||||||
shuttingDown = tvPowerState == 2
|
Text(
|
||||||
|
text = weatherDetails,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = tvTextFontSize,
|
||||||
|
lineHeight = tvTextLineHeight,
|
||||||
|
color = lcdDark,
|
||||||
|
modifier = Modifier.offset(x = (-10).dp),
|
||||||
|
softWrap = false,
|
||||||
|
maxLines = 3
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
fontFamily = FontFamily.Monospace,
|
} else {
|
||||||
textAlign = if (tvPowerState == 1) TextAlign.Start else TextAlign.Center,
|
Text(
|
||||||
color = if (tvPowerState == 1) lcdDark else Color.White,
|
text = if (tvPowerState == 1) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
tvPreviewText(
|
||||||
softWrap = !(tvPowerState == 1 && selectedChannel == TvChannel.NoSignal)
|
channel = selectedChannel,
|
||||||
)
|
frame = tvFrame,
|
||||||
|
weatherLabel = weatherLabel
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
tvPowerOffPreviewText(
|
||||||
|
frame = tvFrame,
|
||||||
|
shuttingDown = tvPowerState == 2
|
||||||
|
)
|
||||||
|
},
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = tvTextFontSize,
|
||||||
|
lineHeight = tvTextLineHeight,
|
||||||
|
textAlign = if (tvPowerState == 1) {
|
||||||
|
TextAlign.Start
|
||||||
|
} else {
|
||||||
|
TextAlign.Center
|
||||||
|
},
|
||||||
|
color = if (tvPowerState == 1) lcdDark else Color.White,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
softWrap = false,
|
||||||
|
maxLines = 5
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,12 +587,19 @@ private fun weatherPreviewText(weatherLabel: String): String {
|
|||||||
val normalizedWeather = weatherLabel.lowercase()
|
val normalizedWeather = weatherLabel.lowercase()
|
||||||
|
|
||||||
val conditionText = when {
|
val conditionText = when {
|
||||||
|
normalizedWeather.contains("snow") -> "SNOWING"
|
||||||
normalizedWeather.contains("rain") -> "RAINING"
|
normalizedWeather.contains("rain") -> "RAINING"
|
||||||
normalizedWeather.contains("cloud") || normalizedWeather.contains("overcast") -> "CLOUDY"
|
normalizedWeather.contains("cloud") || normalizedWeather.contains("overcast") -> "CLOUDY"
|
||||||
else -> "SUNNY"
|
else -> "SUNNY"
|
||||||
}
|
}
|
||||||
|
|
||||||
val iconLines = when (conditionText) {
|
val iconLines = when (conditionText) {
|
||||||
|
"SNOWING" -> listOf(
|
||||||
|
" .--. ",
|
||||||
|
" (____) ",
|
||||||
|
" * * * "
|
||||||
|
)
|
||||||
|
|
||||||
"RAINING" -> listOf(
|
"RAINING" -> listOf(
|
||||||
" .--. ",
|
" .--. ",
|
||||||
" (____) ",
|
" (____) ",
|
||||||
|
|||||||
@@ -0,0 +1,266 @@
|
|||||||
|
package today.notfeelingit.lcdpet
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.workDataOf
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
private const val LCDPET_WORKER_CHANNEL_ID = "lcdpet_alerts"
|
||||||
|
private const val LCDPET_WORKER_URGENT_CHANNEL_ID = "lcdpet_urgent_alerts_v2"
|
||||||
|
|
||||||
|
private const val INPUT_WORK_TYPE = "work_type"
|
||||||
|
private const val INPUT_NOTIFICATION_ID = "notification_id"
|
||||||
|
private const val INPUT_NOTIFICATION_TITLE = "notification_title"
|
||||||
|
private const val INPUT_NOTIFICATION_MESSAGE = "notification_message"
|
||||||
|
private const val INPUT_URGENT_NEED = "urgent_need"
|
||||||
|
|
||||||
|
private const val WORK_TYPE_NOTIFICATION = "notification"
|
||||||
|
private const val WORK_TYPE_WANT = "want"
|
||||||
|
|
||||||
|
private const val LCDPET_BACKGROUND_TEST_WORK = "lcdpet_background_test"
|
||||||
|
|
||||||
|
class LCDPetNotificationWorker(
|
||||||
|
appContext: Context,
|
||||||
|
workerParams: WorkerParameters
|
||||||
|
) : CoroutineWorker(appContext, workerParams) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val workType =
|
||||||
|
inputData.getString(INPUT_WORK_TYPE) ?: WORK_TYPE_NOTIFICATION
|
||||||
|
|
||||||
|
if (workType == WORK_TYPE_WANT) {
|
||||||
|
val createdWant =
|
||||||
|
applicationContext.createLCDPetWantIfDue()
|
||||||
|
?: return Result.success()
|
||||||
|
|
||||||
|
sendLCDPetWorkerNotification(
|
||||||
|
context = applicationContext,
|
||||||
|
notificationId = 2001,
|
||||||
|
title = "LCDPet",
|
||||||
|
message = wantNotificationText(createdWant)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationId = inputData.getInt(INPUT_NOTIFICATION_ID, -1)
|
||||||
|
val title = inputData.getString(INPUT_NOTIFICATION_TITLE) ?: "LCDPet"
|
||||||
|
val message = inputData.getString(INPUT_NOTIFICATION_MESSAGE).orEmpty()
|
||||||
|
val urgentNeed = inputData.getBoolean(INPUT_URGENT_NEED, false)
|
||||||
|
|
||||||
|
if (notificationId < 0 || message.isBlank()) {
|
||||||
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLCDPetWorkerNotification(
|
||||||
|
context = applicationContext,
|
||||||
|
notificationId = notificationId,
|
||||||
|
title = title,
|
||||||
|
message = message,
|
||||||
|
urgentNeed = urgentNeed
|
||||||
|
)
|
||||||
|
|
||||||
|
return Result.success()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scheduleLCDPetNotificationWork(
|
||||||
|
context: Context,
|
||||||
|
uniqueWorkName: String,
|
||||||
|
delayMs: Long,
|
||||||
|
notificationId: Int,
|
||||||
|
message: String,
|
||||||
|
title: String = "LCDPet",
|
||||||
|
urgentNeed: Boolean = false
|
||||||
|
) {
|
||||||
|
val request = OneTimeWorkRequestBuilder<LCDPetNotificationWorker>()
|
||||||
|
.setInitialDelay(delayMs.coerceAtLeast(0L), TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(
|
||||||
|
workDataOf(
|
||||||
|
INPUT_WORK_TYPE to WORK_TYPE_NOTIFICATION,
|
||||||
|
INPUT_NOTIFICATION_ID to notificationId,
|
||||||
|
INPUT_NOTIFICATION_TITLE to title,
|
||||||
|
INPUT_NOTIFICATION_MESSAGE to message,
|
||||||
|
INPUT_URGENT_NEED to urgentNeed
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||||
|
uniqueWorkName,
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scheduleLCDPetWantWork(
|
||||||
|
context: Context,
|
||||||
|
uniqueWorkName: String,
|
||||||
|
delayMs: Long
|
||||||
|
) {
|
||||||
|
val request = OneTimeWorkRequestBuilder<LCDPetNotificationWorker>()
|
||||||
|
.setInitialDelay(
|
||||||
|
delayMs.coerceAtLeast(0L),
|
||||||
|
TimeUnit.MILLISECONDS
|
||||||
|
)
|
||||||
|
.setInputData(
|
||||||
|
workDataOf(
|
||||||
|
INPUT_WORK_TYPE to WORK_TYPE_WANT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
WorkManager.getInstance(context).enqueueUniqueWork(
|
||||||
|
uniqueWorkName,
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelLCDPetNotificationWork(
|
||||||
|
context: Context,
|
||||||
|
uniqueWorkName: String
|
||||||
|
) {
|
||||||
|
WorkManager.getInstance(context).cancelUniqueWork(uniqueWorkName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scheduleLCDPetBackgroundTest(context: Context) {
|
||||||
|
scheduleLCDPetNotificationWork(
|
||||||
|
context = context,
|
||||||
|
uniqueWorkName = LCDPET_BACKGROUND_TEST_WORK,
|
||||||
|
delayMs = 30_000L,
|
||||||
|
notificationId = 1099,
|
||||||
|
message = "Background worker test: beep boop."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun wantNotificationText(
|
||||||
|
createdWant: LCDPetCreatedWant
|
||||||
|
): String {
|
||||||
|
val notificationPetName =
|
||||||
|
createdWant.petName.ifBlank { "Your pet" }
|
||||||
|
|
||||||
|
return when (createdWant.wantName) {
|
||||||
|
"Snack" -> "$notificationPetName wants a snack."
|
||||||
|
"Play" -> "$notificationPetName wants to play."
|
||||||
|
"Pet" -> "$notificationPetName wants pets."
|
||||||
|
"Talk" -> "$notificationPetName is looking for you."
|
||||||
|
"Outside" -> "$notificationPetName wants to go outside."
|
||||||
|
"Bath" -> "$notificationPetName wants a bath."
|
||||||
|
else -> "$notificationPetName wants your attention."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendLCDPetWorkerNotification(
|
||||||
|
context: Context,
|
||||||
|
notificationId: Int,
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
|
urgentNeed: Boolean = false
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
|
||||||
|
ContextCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val manager =
|
||||||
|
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
|
if (urgentNeed) {
|
||||||
|
val urgentSoundUri =
|
||||||
|
Uri.parse(
|
||||||
|
"android.resource://${context.packageName}/${R.raw.need_urgent}"
|
||||||
|
)
|
||||||
|
|
||||||
|
val urgentAudioAttributes =
|
||||||
|
AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val urgentChannel = NotificationChannel(
|
||||||
|
LCDPET_WORKER_URGENT_CHANNEL_ID,
|
||||||
|
"LCDPet Urgent Needs",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = "Urgent Hunger, Potty, Bath, and Energy alerts."
|
||||||
|
setSound(urgentSoundUri, urgentAudioAttributes)
|
||||||
|
enableVibration(true)
|
||||||
|
setVibrationPattern(longArrayOf(0L, 250L))
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.createNotificationChannel(urgentChannel)
|
||||||
|
} else {
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
LCDPET_WORKER_CHANNEL_ID,
|
||||||
|
"LCDPet Alerts",
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
).apply {
|
||||||
|
description = "Pet care reminders and LCDPet status alerts."
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
}
|
||||||
|
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
notificationId,
|
||||||
|
launchIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val channelId =
|
||||||
|
if (urgentNeed) {
|
||||||
|
LCDPET_WORKER_URGENT_CHANNEL_ID
|
||||||
|
} else {
|
||||||
|
LCDPET_WORKER_CHANNEL_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
val builder = NotificationCompat.Builder(context, channelId)
|
||||||
|
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(message)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
|
||||||
|
if (urgentNeed && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
val urgentSoundUri =
|
||||||
|
Uri.parse(
|
||||||
|
"android.resource://${context.packageName}/${R.raw.need_urgent}"
|
||||||
|
)
|
||||||
|
builder.setSound(urgentSoundUri)
|
||||||
|
builder.setVibrate(longArrayOf(0L, 250L))
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat
|
||||||
|
.from(context)
|
||||||
|
.notify(notificationId, builder.build())
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ val Context.lcdPetDataStore by preferencesDataStore(name = "lcdpet_save")
|
|||||||
data class LCDPetRecordSave(
|
data class LCDPetRecordSave(
|
||||||
val bond: Int = 0,
|
val bond: Int = 0,
|
||||||
val petAgeDays: Int = 0,
|
val petAgeDays: Int = 0,
|
||||||
|
val nextAgeAtMs: Long = 0L,
|
||||||
val careScore: Int = 0,
|
val careScore: Int = 0,
|
||||||
val neglectScore: Int = 0,
|
val neglectScore: Int = 0,
|
||||||
val hunger: Int = 5,
|
val hunger: Int = 5,
|
||||||
@@ -24,6 +25,15 @@ data class LCDPetRecordSave(
|
|||||||
val dirtiness: Int = 0,
|
val dirtiness: Int = 0,
|
||||||
val potty: Int = 0,
|
val potty: Int = 0,
|
||||||
val boredom: Int = 0,
|
val boredom: Int = 0,
|
||||||
|
val starvingCount: Int = 0,
|
||||||
|
val recoveryCount: Int = 0,
|
||||||
|
val sickCount: Int = 0,
|
||||||
|
val playEnergyCount: Int = 0,
|
||||||
|
val sameActionStreak: Int = 0,
|
||||||
|
val boredomTriggerAt: Int = 0,
|
||||||
|
val boredomPenaltyCount: Int = 0,
|
||||||
|
val ultraTrollTriggered: Boolean = false,
|
||||||
|
val lastActionName: String = "",
|
||||||
val location: String = "Inside",
|
val location: String = "Inside",
|
||||||
val weather: String = "Sunny",
|
val weather: String = "Sunny",
|
||||||
val status: String = "Nervous",
|
val status: String = "Nervous",
|
||||||
@@ -35,19 +45,34 @@ data class LCDPetRecordSave(
|
|||||||
val tradeInProgress: Int = 0,
|
val tradeInProgress: Int = 0,
|
||||||
val tradeInStartedAtMs: Long = 0L,
|
val tradeInStartedAtMs: Long = 0L,
|
||||||
val currentWant: String = "",
|
val currentWant: String = "",
|
||||||
|
val currentWantStartedAtMs: Long = 0L,
|
||||||
|
val nextWantAtMs: Long = 0L,
|
||||||
val petName: String = "",
|
val petName: String = "",
|
||||||
val petTemplateId: String = "classic",
|
val petTemplateId: String = "classic",
|
||||||
val isSleeping: Boolean = false,
|
val isSleeping: Boolean = false,
|
||||||
val sleepMode: String = "",
|
val sleepMode: String = "",
|
||||||
val roughWakeAlert: Boolean = false,
|
val roughWakeAlert: Boolean = false,
|
||||||
val autoSleepStartedAtMs: Long = 0L,
|
val autoSleepStartedAtMs: Long = 0L,
|
||||||
|
val outsideEnergyNextTickAtMs: Long = 0L,
|
||||||
|
val nextWeatherAtMs: Long = 0L,
|
||||||
|
val bondReadyAtMs: Long = 0L,
|
||||||
|
val careCycleNextTickAtMs: Long = 0L,
|
||||||
|
val rainHappinessNextTickAtMs: Long = 0L,
|
||||||
|
val pottyNextTickAtMs: Long = 0L,
|
||||||
|
val dirtyHealthNextTickAtMs: Long = 0L,
|
||||||
val careJournalRaw: String = "",
|
val careJournalRaw: String = "",
|
||||||
val lastSavedAtMs: Long = System.currentTimeMillis()
|
val lastSavedAtMs: Long = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
data class LCDPetCreatedWant(
|
||||||
|
val wantName: String,
|
||||||
|
val petName: String
|
||||||
|
)
|
||||||
|
|
||||||
private object LCDPetSaveKeys {
|
private object LCDPetSaveKeys {
|
||||||
val BOND = intPreferencesKey("bond")
|
val BOND = intPreferencesKey("bond")
|
||||||
val PET_AGE_DAYS = intPreferencesKey("pet_age_days")
|
val PET_AGE_DAYS = intPreferencesKey("pet_age_days")
|
||||||
|
val NEXT_AGE_AT_MS = longPreferencesKey("next_age_at_ms")
|
||||||
val CARE_SCORE = intPreferencesKey("care_score")
|
val CARE_SCORE = intPreferencesKey("care_score")
|
||||||
val NEGLECT_SCORE = intPreferencesKey("neglect_score")
|
val NEGLECT_SCORE = intPreferencesKey("neglect_score")
|
||||||
val HUNGER = intPreferencesKey("hunger")
|
val HUNGER = intPreferencesKey("hunger")
|
||||||
@@ -57,6 +82,17 @@ private object LCDPetSaveKeys {
|
|||||||
val DIRTINESS = intPreferencesKey("dirtiness")
|
val DIRTINESS = intPreferencesKey("dirtiness")
|
||||||
val POTTY = intPreferencesKey("potty")
|
val POTTY = intPreferencesKey("potty")
|
||||||
val BOREDOM = intPreferencesKey("boredom")
|
val BOREDOM = intPreferencesKey("boredom")
|
||||||
|
val STARVING_COUNT = intPreferencesKey("starving_count")
|
||||||
|
val RECOVERY_COUNT = intPreferencesKey("recovery_count")
|
||||||
|
val SICK_COUNT = intPreferencesKey("sick_count")
|
||||||
|
val PLAY_ENERGY_COUNT = intPreferencesKey("play_energy_count")
|
||||||
|
val SAME_ACTION_STREAK = intPreferencesKey("same_action_streak")
|
||||||
|
val BOREDOM_TRIGGER_AT = intPreferencesKey("boredom_trigger_at")
|
||||||
|
val BOREDOM_PENALTY_COUNT =
|
||||||
|
intPreferencesKey("boredom_penalty_count")
|
||||||
|
val ULTRA_TROLL_TRIGGERED =
|
||||||
|
booleanPreferencesKey("ultra_troll_triggered")
|
||||||
|
val LAST_ACTION_NAME = stringPreferencesKey("last_action_name")
|
||||||
val LOCATION = stringPreferencesKey("location")
|
val LOCATION = stringPreferencesKey("location")
|
||||||
val WEATHER = stringPreferencesKey("weather")
|
val WEATHER = stringPreferencesKey("weather")
|
||||||
val STATUS = stringPreferencesKey("status")
|
val STATUS = stringPreferencesKey("status")
|
||||||
@@ -68,13 +104,32 @@ private object LCDPetSaveKeys {
|
|||||||
val TRADE_IN_PROGRESS = intPreferencesKey("trade_in_progress")
|
val TRADE_IN_PROGRESS = intPreferencesKey("trade_in_progress")
|
||||||
val TRADE_IN_STARTED_AT_MS = longPreferencesKey("trade_in_started_at_ms")
|
val TRADE_IN_STARTED_AT_MS = longPreferencesKey("trade_in_started_at_ms")
|
||||||
val CURRENT_WANT = stringPreferencesKey("current_want")
|
val CURRENT_WANT = stringPreferencesKey("current_want")
|
||||||
|
val CURRENT_WANT_STARTED_AT_MS =
|
||||||
|
longPreferencesKey("current_want_started_at_ms")
|
||||||
|
val NEXT_WANT_AT_MS = longPreferencesKey("next_want_at_ms")
|
||||||
val PET_NAME = stringPreferencesKey("pet_name")
|
val PET_NAME = stringPreferencesKey("pet_name")
|
||||||
val PET_TEMPLATE_ID = stringPreferencesKey("pet_template_id")
|
val PET_TEMPLATE_ID = stringPreferencesKey("pet_template_id")
|
||||||
val IS_SLEEPING = booleanPreferencesKey("is_sleeping")
|
val IS_SLEEPING = booleanPreferencesKey("is_sleeping")
|
||||||
val SLEEP_MODE = stringPreferencesKey("sleep_mode")
|
val SLEEP_MODE = stringPreferencesKey("sleep_mode")
|
||||||
val ROUGH_WAKE_ALERT = booleanPreferencesKey("rough_wake_alert")
|
val ROUGH_WAKE_ALERT = booleanPreferencesKey("rough_wake_alert")
|
||||||
val AUTO_SLEEP_STARTED_AT_MS = longPreferencesKey("auto_sleep_started_at_ms")
|
val AUTO_SLEEP_STARTED_AT_MS = longPreferencesKey("auto_sleep_started_at_ms")
|
||||||
|
val OUTSIDE_ENERGY_NEXT_TICK_AT_MS =
|
||||||
|
longPreferencesKey("outside_energy_next_tick_at_ms")
|
||||||
|
val NEXT_WEATHER_AT_MS =
|
||||||
|
longPreferencesKey("next_weather_at_ms")
|
||||||
|
val BOND_READY_AT_MS =
|
||||||
|
longPreferencesKey("bond_ready_at_ms")
|
||||||
|
val CARE_CYCLE_NEXT_TICK_AT_MS =
|
||||||
|
longPreferencesKey("care_cycle_next_tick_at_ms")
|
||||||
|
val RAIN_HAPPINESS_NEXT_TICK_AT_MS =
|
||||||
|
longPreferencesKey("rain_happiness_next_tick_at_ms")
|
||||||
|
val POTTY_NEXT_TICK_AT_MS =
|
||||||
|
longPreferencesKey("potty_next_tick_at_ms")
|
||||||
|
val DIRTY_HEALTH_NEXT_TICK_AT_MS =
|
||||||
|
longPreferencesKey("dirty_health_next_tick_at_ms")
|
||||||
val CARE_JOURNAL = stringPreferencesKey("care_journal")
|
val CARE_JOURNAL = stringPreferencesKey("care_journal")
|
||||||
|
val APPEARANCE = stringPreferencesKey("appearance")
|
||||||
|
val PET_HOME_COLOR = stringPreferencesKey("pet_home_color")
|
||||||
val LAST_SAVED_AT_MS = longPreferencesKey("last_saved_at_ms")
|
val LAST_SAVED_AT_MS = longPreferencesKey("last_saved_at_ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +138,8 @@ val Context.lcdPetRecordSaveFlow: Flow<LCDPetRecordSave>
|
|||||||
LCDPetRecordSave(
|
LCDPetRecordSave(
|
||||||
bond = preferences[LCDPetSaveKeys.BOND] ?: 0,
|
bond = preferences[LCDPetSaveKeys.BOND] ?: 0,
|
||||||
petAgeDays = preferences[LCDPetSaveKeys.PET_AGE_DAYS] ?: 0,
|
petAgeDays = preferences[LCDPetSaveKeys.PET_AGE_DAYS] ?: 0,
|
||||||
|
nextAgeAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_AGE_AT_MS] ?: 0L,
|
||||||
careScore = preferences[LCDPetSaveKeys.CARE_SCORE] ?: 0,
|
careScore = preferences[LCDPetSaveKeys.CARE_SCORE] ?: 0,
|
||||||
neglectScore = preferences[LCDPetSaveKeys.NEGLECT_SCORE] ?: 0,
|
neglectScore = preferences[LCDPetSaveKeys.NEGLECT_SCORE] ?: 0,
|
||||||
hunger = preferences[LCDPetSaveKeys.HUNGER] ?: 5,
|
hunger = preferences[LCDPetSaveKeys.HUNGER] ?: 5,
|
||||||
@@ -92,6 +149,24 @@ val Context.lcdPetRecordSaveFlow: Flow<LCDPetRecordSave>
|
|||||||
dirtiness = preferences[LCDPetSaveKeys.DIRTINESS] ?: 0,
|
dirtiness = preferences[LCDPetSaveKeys.DIRTINESS] ?: 0,
|
||||||
potty = preferences[LCDPetSaveKeys.POTTY] ?: 0,
|
potty = preferences[LCDPetSaveKeys.POTTY] ?: 0,
|
||||||
boredom = preferences[LCDPetSaveKeys.BOREDOM] ?: 0,
|
boredom = preferences[LCDPetSaveKeys.BOREDOM] ?: 0,
|
||||||
|
starvingCount =
|
||||||
|
preferences[LCDPetSaveKeys.STARVING_COUNT] ?: 0,
|
||||||
|
recoveryCount =
|
||||||
|
preferences[LCDPetSaveKeys.RECOVERY_COUNT] ?: 0,
|
||||||
|
sickCount =
|
||||||
|
preferences[LCDPetSaveKeys.SICK_COUNT] ?: 0,
|
||||||
|
playEnergyCount =
|
||||||
|
preferences[LCDPetSaveKeys.PLAY_ENERGY_COUNT] ?: 0,
|
||||||
|
sameActionStreak =
|
||||||
|
preferences[LCDPetSaveKeys.SAME_ACTION_STREAK] ?: 0,
|
||||||
|
boredomTriggerAt =
|
||||||
|
preferences[LCDPetSaveKeys.BOREDOM_TRIGGER_AT] ?: 0,
|
||||||
|
boredomPenaltyCount =
|
||||||
|
preferences[LCDPetSaveKeys.BOREDOM_PENALTY_COUNT] ?: 0,
|
||||||
|
ultraTrollTriggered =
|
||||||
|
preferences[LCDPetSaveKeys.ULTRA_TROLL_TRIGGERED] ?: false,
|
||||||
|
lastActionName =
|
||||||
|
preferences[LCDPetSaveKeys.LAST_ACTION_NAME] ?: "",
|
||||||
location = preferences[LCDPetSaveKeys.LOCATION] ?: "Inside",
|
location = preferences[LCDPetSaveKeys.LOCATION] ?: "Inside",
|
||||||
weather = preferences[LCDPetSaveKeys.WEATHER] ?: "Sunny",
|
weather = preferences[LCDPetSaveKeys.WEATHER] ?: "Sunny",
|
||||||
status = preferences[LCDPetSaveKeys.STATUS] ?: "Nervous",
|
status = preferences[LCDPetSaveKeys.STATUS] ?: "Nervous",
|
||||||
@@ -103,17 +178,73 @@ val Context.lcdPetRecordSaveFlow: Flow<LCDPetRecordSave>
|
|||||||
tradeInProgress = preferences[LCDPetSaveKeys.TRADE_IN_PROGRESS] ?: 0,
|
tradeInProgress = preferences[LCDPetSaveKeys.TRADE_IN_PROGRESS] ?: 0,
|
||||||
tradeInStartedAtMs = preferences[LCDPetSaveKeys.TRADE_IN_STARTED_AT_MS] ?: 0L,
|
tradeInStartedAtMs = preferences[LCDPetSaveKeys.TRADE_IN_STARTED_AT_MS] ?: 0L,
|
||||||
currentWant = preferences[LCDPetSaveKeys.CURRENT_WANT] ?: "",
|
currentWant = preferences[LCDPetSaveKeys.CURRENT_WANT] ?: "",
|
||||||
|
currentWantStartedAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT_STARTED_AT_MS] ?: 0L,
|
||||||
|
nextWantAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WANT_AT_MS] ?: 0L,
|
||||||
petName = preferences[LCDPetSaveKeys.PET_NAME] ?: "",
|
petName = preferences[LCDPetSaveKeys.PET_NAME] ?: "",
|
||||||
petTemplateId = preferences[LCDPetSaveKeys.PET_TEMPLATE_ID] ?: "classic",
|
petTemplateId = preferences[LCDPetSaveKeys.PET_TEMPLATE_ID] ?: "classic",
|
||||||
isSleeping = preferences[LCDPetSaveKeys.IS_SLEEPING] ?: false,
|
isSleeping = preferences[LCDPetSaveKeys.IS_SLEEPING] ?: false,
|
||||||
sleepMode = preferences[LCDPetSaveKeys.SLEEP_MODE] ?: "",
|
sleepMode = preferences[LCDPetSaveKeys.SLEEP_MODE] ?: "",
|
||||||
roughWakeAlert = preferences[LCDPetSaveKeys.ROUGH_WAKE_ALERT] ?: false,
|
roughWakeAlert = preferences[LCDPetSaveKeys.ROUGH_WAKE_ALERT] ?: false,
|
||||||
autoSleepStartedAtMs = preferences[LCDPetSaveKeys.AUTO_SLEEP_STARTED_AT_MS] ?: 0L,
|
autoSleepStartedAtMs = preferences[LCDPetSaveKeys.AUTO_SLEEP_STARTED_AT_MS] ?: 0L,
|
||||||
|
outsideEnergyNextTickAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.OUTSIDE_ENERGY_NEXT_TICK_AT_MS] ?: 0L,
|
||||||
|
nextWeatherAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WEATHER_AT_MS] ?: 0L,
|
||||||
|
bondReadyAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.BOND_READY_AT_MS] ?: 0L,
|
||||||
|
careCycleNextTickAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.CARE_CYCLE_NEXT_TICK_AT_MS] ?: 0L,
|
||||||
|
rainHappinessNextTickAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.RAIN_HAPPINESS_NEXT_TICK_AT_MS] ?: 0L,
|
||||||
|
pottyNextTickAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.POTTY_NEXT_TICK_AT_MS] ?: 0L,
|
||||||
|
dirtyHealthNextTickAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.DIRTY_HEALTH_NEXT_TICK_AT_MS] ?: 0L,
|
||||||
careJournalRaw = preferences[LCDPetSaveKeys.CARE_JOURNAL] ?: "",
|
careJournalRaw = preferences[LCDPetSaveKeys.CARE_JOURNAL] ?: "",
|
||||||
lastSavedAtMs = preferences[LCDPetSaveKeys.LAST_SAVED_AT_MS] ?: System.currentTimeMillis()
|
lastSavedAtMs = preferences[LCDPetSaveKeys.LAST_SAVED_AT_MS] ?: System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val Context.lcdPetAppearanceFlow: Flow<String>
|
||||||
|
get() = lcdPetDataStore.data.map { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.APPEARANCE] ?: "System"
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.setLCDPetAppearance(appearance: String) {
|
||||||
|
val savedAppearance =
|
||||||
|
appearance.takeIf { it in setOf("System", "Light", "Dark") }
|
||||||
|
?: "System"
|
||||||
|
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.APPEARANCE] = savedAppearance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Context.lcdPetHomeColorFlow: Flow<String>
|
||||||
|
get() = lcdPetDataStore.data.map { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.PET_HOME_COLOR] ?: "LCD"
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.setLCDPetHomeColor(homeColor: String) {
|
||||||
|
val savedHomeColor =
|
||||||
|
homeColor.takeIf {
|
||||||
|
it in setOf("LCD", "R", "G", "B", "Y", "P", "K")
|
||||||
|
} ?: "LCD"
|
||||||
|
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.PET_HOME_COLOR] = savedHomeColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.setLCDPetNextAgeAtMs(nextAgeAtMs: Long) {
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_AGE_AT_MS] =
|
||||||
|
nextAgeAtMs.coerceAtLeast(0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun Context.saveLCDPetStats(
|
suspend fun Context.saveLCDPetStats(
|
||||||
bond: Int,
|
bond: Int,
|
||||||
petAgeDays: Int,
|
petAgeDays: Int,
|
||||||
@@ -126,6 +257,15 @@ suspend fun Context.saveLCDPetStats(
|
|||||||
dirtiness: Int,
|
dirtiness: Int,
|
||||||
potty: Int,
|
potty: Int,
|
||||||
boredom: Int,
|
boredom: Int,
|
||||||
|
starvingCount: Int,
|
||||||
|
recoveryCount: Int,
|
||||||
|
sickCount: Int,
|
||||||
|
playEnergyCount: Int,
|
||||||
|
sameActionStreak: Int,
|
||||||
|
boredomTriggerAt: Int,
|
||||||
|
boredomPenaltyCount: Int,
|
||||||
|
ultraTrollTriggered: Boolean,
|
||||||
|
lastActionName: String,
|
||||||
location: String,
|
location: String,
|
||||||
weather: String,
|
weather: String,
|
||||||
status: String,
|
status: String,
|
||||||
@@ -143,6 +283,14 @@ suspend fun Context.saveLCDPetStats(
|
|||||||
sleepMode: String,
|
sleepMode: String,
|
||||||
roughWakeAlert: Boolean,
|
roughWakeAlert: Boolean,
|
||||||
autoSleepStartedAtMs: Long,
|
autoSleepStartedAtMs: Long,
|
||||||
|
currentWantStartedAtMs: Long = 0L,
|
||||||
|
nextWantAtMs: Long = 0L,
|
||||||
|
nextWeatherAtMs: Long,
|
||||||
|
bondReadyAtMs: Long,
|
||||||
|
careCycleNextTickAtMs: Long,
|
||||||
|
rainHappinessNextTickAtMs: Long,
|
||||||
|
pottyNextTickAtMs: Long,
|
||||||
|
dirtyHealthNextTickAtMs: Long,
|
||||||
careJournalRaw: String? = null
|
careJournalRaw: String? = null
|
||||||
) {
|
) {
|
||||||
lcdPetDataStore.edit { preferences ->
|
lcdPetDataStore.edit { preferences ->
|
||||||
@@ -157,6 +305,24 @@ suspend fun Context.saveLCDPetStats(
|
|||||||
preferences[LCDPetSaveKeys.DIRTINESS] = dirtiness
|
preferences[LCDPetSaveKeys.DIRTINESS] = dirtiness
|
||||||
preferences[LCDPetSaveKeys.POTTY] = potty
|
preferences[LCDPetSaveKeys.POTTY] = potty
|
||||||
preferences[LCDPetSaveKeys.BOREDOM] = boredom
|
preferences[LCDPetSaveKeys.BOREDOM] = boredom
|
||||||
|
preferences[LCDPetSaveKeys.STARVING_COUNT] =
|
||||||
|
starvingCount.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.RECOVERY_COUNT] =
|
||||||
|
recoveryCount.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.SICK_COUNT] =
|
||||||
|
sickCount.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.PLAY_ENERGY_COUNT] =
|
||||||
|
playEnergyCount.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.SAME_ACTION_STREAK] =
|
||||||
|
sameActionStreak.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.BOREDOM_TRIGGER_AT] =
|
||||||
|
boredomTriggerAt.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.BOREDOM_PENALTY_COUNT] =
|
||||||
|
boredomPenaltyCount.coerceAtLeast(0)
|
||||||
|
preferences[LCDPetSaveKeys.ULTRA_TROLL_TRIGGERED] =
|
||||||
|
ultraTrollTriggered
|
||||||
|
preferences[LCDPetSaveKeys.LAST_ACTION_NAME] =
|
||||||
|
lastActionName
|
||||||
preferences[LCDPetSaveKeys.LOCATION] = location
|
preferences[LCDPetSaveKeys.LOCATION] = location
|
||||||
preferences[LCDPetSaveKeys.WEATHER] = weather
|
preferences[LCDPetSaveKeys.WEATHER] = weather
|
||||||
preferences[LCDPetSaveKeys.STATUS] = status
|
preferences[LCDPetSaveKeys.STATUS] = status
|
||||||
@@ -168,6 +334,22 @@ suspend fun Context.saveLCDPetStats(
|
|||||||
preferences[LCDPetSaveKeys.TRADE_IN_PROGRESS] = tradeInProgress
|
preferences[LCDPetSaveKeys.TRADE_IN_PROGRESS] = tradeInProgress
|
||||||
preferences[LCDPetSaveKeys.TRADE_IN_STARTED_AT_MS] = tradeInStartedAtMs
|
preferences[LCDPetSaveKeys.TRADE_IN_STARTED_AT_MS] = tradeInStartedAtMs
|
||||||
preferences[LCDPetSaveKeys.CURRENT_WANT] = currentWant
|
preferences[LCDPetSaveKeys.CURRENT_WANT] = currentWant
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT_STARTED_AT_MS] =
|
||||||
|
currentWantStartedAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WANT_AT_MS] =
|
||||||
|
nextWantAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WEATHER_AT_MS] =
|
||||||
|
nextWeatherAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.BOND_READY_AT_MS] =
|
||||||
|
bondReadyAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.CARE_CYCLE_NEXT_TICK_AT_MS] =
|
||||||
|
careCycleNextTickAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.RAIN_HAPPINESS_NEXT_TICK_AT_MS] =
|
||||||
|
rainHappinessNextTickAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.POTTY_NEXT_TICK_AT_MS] =
|
||||||
|
pottyNextTickAtMs.coerceAtLeast(0L)
|
||||||
|
preferences[LCDPetSaveKeys.DIRTY_HEALTH_NEXT_TICK_AT_MS] =
|
||||||
|
dirtyHealthNextTickAtMs.coerceAtLeast(0L)
|
||||||
preferences[LCDPetSaveKeys.PET_NAME] = petName
|
preferences[LCDPetSaveKeys.PET_NAME] = petName
|
||||||
preferences[LCDPetSaveKeys.PET_TEMPLATE_ID] = petTemplateId
|
preferences[LCDPetSaveKeys.PET_TEMPLATE_ID] = petTemplateId
|
||||||
preferences[LCDPetSaveKeys.IS_SLEEPING] = isSleeping
|
preferences[LCDPetSaveKeys.IS_SLEEPING] = isSleeping
|
||||||
@@ -180,3 +362,88 @@ suspend fun Context.saveLCDPetStats(
|
|||||||
preferences[LCDPetSaveKeys.LAST_SAVED_AT_MS] = System.currentTimeMillis()
|
preferences[LCDPetSaveKeys.LAST_SAVED_AT_MS] = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun Context.createLCDPetWantIfDue(
|
||||||
|
nowMs: Long = System.currentTimeMillis()
|
||||||
|
): LCDPetCreatedWant? {
|
||||||
|
var createdWant: LCDPetCreatedWant? = null
|
||||||
|
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
val currentWant =
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT].orEmpty()
|
||||||
|
val nextWantAtMs =
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WANT_AT_MS] ?: 0L
|
||||||
|
val petName =
|
||||||
|
preferences[LCDPetSaveKeys.PET_NAME].orEmpty()
|
||||||
|
val location =
|
||||||
|
preferences[LCDPetSaveKeys.LOCATION] ?: "Inside"
|
||||||
|
val bond =
|
||||||
|
preferences[LCDPetSaveKeys.BOND] ?: 0
|
||||||
|
val status =
|
||||||
|
preferences[LCDPetSaveKeys.STATUS] ?: "Nervous"
|
||||||
|
val isSleeping =
|
||||||
|
preferences[LCDPetSaveKeys.IS_SLEEPING] ?: false
|
||||||
|
val isTradingInPet =
|
||||||
|
preferences[LCDPetSaveKeys.IS_TRADING_IN_PET] ?: false
|
||||||
|
val isFindingNewPet =
|
||||||
|
preferences[LCDPetSaveKeys.IS_FINDING_NEW_PET] ?: false
|
||||||
|
|
||||||
|
val canCreateWant =
|
||||||
|
currentWant.isBlank() &&
|
||||||
|
nextWantAtMs > 0L &&
|
||||||
|
nowMs >= nextWantAtMs &&
|
||||||
|
petName.isNotBlank() &&
|
||||||
|
status !in setOf("Sick", "Weak") &&
|
||||||
|
!isSleeping &&
|
||||||
|
!isTradingInPet &&
|
||||||
|
!isFindingNewPet
|
||||||
|
|
||||||
|
if (!canCreateWant) {
|
||||||
|
return@edit
|
||||||
|
}
|
||||||
|
|
||||||
|
val possibleWants =
|
||||||
|
if (location == "Inside") {
|
||||||
|
listOf("Snack", "Play", "Pet", "Talk", "Outside") +
|
||||||
|
if (bond >= 100) listOf("Bath") else emptyList()
|
||||||
|
} else {
|
||||||
|
listOf("Play", "Pet", "Talk")
|
||||||
|
}
|
||||||
|
|
||||||
|
val wantName = possibleWants.random()
|
||||||
|
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT] = wantName
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT_STARTED_AT_MS] = nowMs
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WANT_AT_MS] = 0L
|
||||||
|
preferences[LCDPetSaveKeys.LAST_SAVED_AT_MS] = nowMs
|
||||||
|
|
||||||
|
createdWant = LCDPetCreatedWant(
|
||||||
|
wantName = wantName,
|
||||||
|
petName = petName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdWant
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.clearLCDPetWants() {
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT] = ""
|
||||||
|
preferences[LCDPetSaveKeys.CURRENT_WANT_STARTED_AT_MS] = 0L
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WANT_AT_MS] = 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.setLCDPetNextWantAtMs(nextWantAtMs: Long) {
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.NEXT_WANT_AT_MS] =
|
||||||
|
nextWantAtMs.coerceAtLeast(0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun Context.setLCDPetOutsideEnergyNextTickAtMs(nextTickAtMs: Long) {
|
||||||
|
lcdPetDataStore.edit { preferences ->
|
||||||
|
preferences[LCDPetSaveKeys.OUTSIDE_ENERGY_NEXT_TICK_AT_MS] =
|
||||||
|
nextTickAtMs.coerceAtLeast(0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package today.notfeelingit.lcdpet.ui.theme
|
package today.notfeelingit.lcdpet.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
@@ -8,24 +10,23 @@ private val LightColorScheme = lightColorScheme(
|
|||||||
primary = Purple40,
|
primary = Purple40,
|
||||||
secondary = PurpleGrey40,
|
secondary = PurpleGrey40,
|
||||||
tertiary = Pink40
|
tertiary = Pink40
|
||||||
|
)
|
||||||
|
|
||||||
/* Other default colors to override
|
private val DarkColorScheme = darkColorScheme(
|
||||||
background = Color(0xFFFFFBFE),
|
primary = Purple80,
|
||||||
surface = Color(0xFFFFFBFE),
|
secondary = PurpleGrey80,
|
||||||
onPrimary = Color.White,
|
tertiary = Pink80
|
||||||
onSecondary = Color.White,
|
|
||||||
onTertiary = Color.White,
|
|
||||||
onBackground = Color(0xFF1C1B1F),
|
|
||||||
onSurface = Color(0xFF1C1B1F),
|
|
||||||
*/
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LCDPetTheme(
|
fun LCDPetTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = LightColorScheme,
|
colorScheme =
|
||||||
|
if (darkTheme) DarkColorScheme
|
||||||
|
else LightColorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
|||||||
|
LCDPet 1.2 adds:
|
||||||
|
|
||||||
|
• Snow weather and Home Renovation colors
|
||||||
|
• Full meals, Snack Wants, and improved Want safety
|
||||||
|
• Pet activity sounds and sound-detected TALK
|
||||||
|
• Better notification sound, vibration, and foreground behavior
|
||||||
|
• Responsive layouts, aligned status meters, and appearance controls
|
||||||
|
• Continuous TV channels that resume in place
|
||||||
|
• Improved Age, Nap, care-cycle, weather, Bond, Potty, and health persistence
|
||||||
|
• Numerous notification, lifecycle, and stability fixes
|
||||||
Reference in New Issue
Block a user