Compare commits

...

3 Commits

Author SHA1 Message Date
Warren H 5aac39c6c9 Android build v1.0.42 2026-06-20 20:49:46 -04:00
Warren H 224249e372 Android build v1.0.41 2026-06-20 20:42:10 -04:00
Warren H 66f5af4484 Android build v1.0.40 2026-06-20 20:16:20 -04:00
7 changed files with 58 additions and 73 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -7,8 +7,8 @@ android {
applicationId "com.warren.iwanttoheal" applicationId "com.warren.iwanttoheal"
minSdkVersion rootProject.ext.minSdkVersion minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 59 versionCode 62
versionName "1.0.39" versionName "1.0.42"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions { aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
+29 -33
View File
@@ -1795,6 +1795,23 @@ h2 {
font-size: 14px; font-size: 14px;
} }
.run-title-row {
align-items: center;
display: flex;
gap: 10px;
justify-content: space-between;
}
.run-title-row h2 {
min-width: 0;
}
.inline-back-button {
flex: 0 0 auto;
min-height: 34px;
padding: 6px 10px;
}
.run-setup-heading small { .run-setup-heading small {
color: var(--muted); color: var(--muted);
font-size: 16px; font-size: 16px;
@@ -1952,16 +1969,6 @@ h2 {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.part-start-row {
display: grid;
gap: 8px;
grid-template-columns: minmax(0, 1fr) minmax(82px, 0.38fr);
}
.hard-mode-button {
border-color: #c25b4b;
}
.part-setup-panel .primary-button { .part-setup-panel .primary-button {
min-height: 54px; min-height: 54px;
} }
@@ -2054,14 +2061,6 @@ h2 {
padding: 10px; padding: 10px;
} }
.dungeon-run-screen .screen-heading {
padding-bottom: 10px;
}
.dungeon-run-screen .screen-heading h1 {
font-size: 18px;
}
.dungeon-run-board, .dungeon-run-board,
.dungeon-run-main { .dungeon-run-main {
gap: 10px; gap: 10px;
@@ -2098,6 +2097,12 @@ h2 {
font-size: 12px; font-size: 12px;
} }
.inline-back-button {
font-size: 12px;
min-height: 28px;
padding: 4px 7px;
}
.activity-pager button { .activity-pager button {
font-size: 9px; font-size: 9px;
min-height: 28px; min-height: 28px;
@@ -2246,21 +2251,6 @@ h2 {
padding: 6px; padding: 6px;
} }
.dungeon-run-screen .screen-heading {
padding-bottom: 4px;
}
.dungeon-run-screen .screen-heading h1 {
font-size: 13px;
line-height: 1.15;
}
.dungeon-run-screen .back-button {
font-size: 14px;
min-height: 34px;
padding: 5px 8px;
}
.dungeon-run-board, .dungeon-run-board,
.dungeon-run-main, .dungeon-run-main,
.dungeon-setup-rail { .dungeon-setup-rail {
@@ -2297,6 +2287,12 @@ h2 {
line-height: 1.2; line-height: 1.2;
} }
.inline-back-button {
font-size: 10px;
min-height: 24px;
padding: 3px 6px;
}
.dungeon-run-screen .eyebrow, .dungeon-run-screen .eyebrow,
.dungeon-run-screen .tier-grid strong, .dungeon-run-screen .tier-grid strong,
.dungeon-choice-grid .activity-card strong, .dungeon-choice-grid .activity-card strong,
+21 -32
View File
@@ -55,7 +55,7 @@ const MENU_ITEMS: Array<{
const LAST_DIFFICULTY_KEY = 'i-want-to-heal:last-difficulty' const LAST_DIFFICULTY_KEY = 'i-want-to-heal:last-difficulty'
const SHOW_LEADERBOARDS = false const SHOW_LEADERBOARDS = false
const ACTIVITY_PAGE_SIZE = 6 const ACTIVITY_PAGE_SIZE = 4
function activityInitials(name: string) { function activityInitials(name: string) {
return name return name
@@ -88,7 +88,6 @@ function App() {
const [roguelikeUpgradeTiming, setRoguelikeUpgradeTiming] = useState<RoguelikeUpgradeTiming>('encounter') const [roguelikeUpgradeTiming, setRoguelikeUpgradeTiming] = useState<RoguelikeUpgradeTiming>('encounter')
const [roguelikeAbilityLabelMode, setRoguelikeAbilityLabelMode] = useState<RoguelikeAbilityLabelMode>('ability') const [roguelikeAbilityLabelMode, setRoguelikeAbilityLabelMode] = useState<RoguelikeAbilityLabelMode>('ability')
const [pvpContentType, setPvpContentType] = useState<PvpContentType>('dungeon') const [pvpContentType, setPvpContentType] = useState<PvpContentType>('dungeon')
const [selectedPart, setSelectedPart] = useState(1)
const [selectedMarathonMode, setSelectedMarathonMode] = useState(false) const [selectedMarathonMode, setSelectedMarathonMode] = useState(false)
const [activityPage, setActivityPage] = useState(0) const [activityPage, setActivityPage] = useState(0)
const [combatContentId, setCombatContentId] = useState(1) const [combatContentId, setCombatContentId] = useState(1)
@@ -233,7 +232,6 @@ function App() {
const roguelikePool = profile.dungeons const roguelikePool = profile.dungeons
.filter((candidate) => candidate.contentType === roguelikeKind) .filter((candidate) => candidate.contentType === roguelikeKind)
.flatMap((candidate) => candidate.encounters) .flatMap((candidate) => candidate.encounters)
const startPart = selectedPart
return ( return (
<CombatScreen <CombatScreen
difficulty={difficulty} difficulty={difficulty}
@@ -245,7 +243,7 @@ function App() {
roguelikeUpgradeTiming={combatContentId < 0 ? roguelikeUpgradeTiming : undefined} roguelikeUpgradeTiming={combatContentId < 0 ? roguelikeUpgradeTiming : undefined}
roguelikeAbilityLabelMode={combatContentId < 0 ? roguelikeAbilityLabelMode : undefined} roguelikeAbilityLabelMode={combatContentId < 0 ? roguelikeAbilityLabelMode : undefined}
roguelikeEncounterPool={combatContentId < 0 ? roguelikePool : undefined} roguelikeEncounterPool={combatContentId < 0 ? roguelikePool : undefined}
startPart={startPart} startPart={1}
onExit={() => { onExit={() => {
setScreen(combatContentId < 0 ? 'roguelike' : dungeon.contentType === 'raid' ? 'raids' : 'dungeons') setScreen(combatContentId < 0 ? 'roguelike' : dungeon.contentType === 'raid' ? 'raids' : 'dungeons')
}} }}
@@ -298,7 +296,6 @@ function App() {
setCombatContentId(-1) setCombatContentId(-1)
setSelectedDifficultyId(baseDungeon?.difficulties[0]?.id ?? 1) setSelectedDifficultyId(baseDungeon?.difficulties[0]?.id ?? 1)
} }
setSelectedPart(1)
setSelectedMarathonMode(false) setSelectedMarathonMode(false)
setScreen('combat') setScreen('combat')
} }
@@ -318,26 +315,24 @@ function App() {
?? tierOptions.slice().reverse().find((candidate) => profile.character.level >= candidate.unlockLevel) ?? tierOptions.slice().reverse().find((candidate) => profile.character.level >= candidate.unlockLevel)
?? tierOptions[0] ?? tierOptions[0]
const selectedTierItemLevel = selectedTier?.droppedItemLevel ?? 0 const selectedTierItemLevel = selectedTier?.droppedItemLevel ?? 0
const tierActivityOptions = activityOptions.filter((option) => const activityPageCount = Math.max(1, Math.ceil(activityOptions.length / ACTIVITY_PAGE_SIZE))
option.difficulties.some((difficulty) => difficulty.droppedItemLevel === selectedTierItemLevel),
)
const activityPageCount = Math.max(1, Math.ceil(tierActivityOptions.length / ACTIVITY_PAGE_SIZE))
const currentActivityPage = Math.min(activityPage, activityPageCount - 1) const currentActivityPage = Math.min(activityPage, activityPageCount - 1)
const pagedActivityOptions = tierActivityOptions.slice( const pagedActivityOptions = activityOptions.slice(
currentActivityPage * ACTIVITY_PAGE_SIZE, currentActivityPage * ACTIVITY_PAGE_SIZE,
currentActivityPage * ACTIVITY_PAGE_SIZE + ACTIVITY_PAGE_SIZE, currentActivityPage * ACTIVITY_PAGE_SIZE + ACTIVITY_PAGE_SIZE,
) )
const activityPageStart = activityOptions.length === 0
? 0
: currentActivityPage * ACTIVITY_PAGE_SIZE + 1
const activityPageEnd = Math.min(activityOptions.length, (currentActivityPage + 1) * ACTIVITY_PAGE_SIZE)
const selectedActivityId = screen === 'raids' && raid ? raid.id : dungeon.id const selectedActivityId = screen === 'raids' && raid ? raid.id : dungeon.id
const activity = tierActivityOptions.find((candidate) => candidate.id === selectedActivityId) const activity = activityOptions.find((candidate) => candidate.id === selectedActivityId)
?? tierActivityOptions[0] ?? activityOptions[0]
?? (screen === 'raids' && raid ? raid : dungeon) ?? (screen === 'raids' && raid ? raid : dungeon)
const selectedDifficulty = activity.difficulties.find( const selectedDifficulty = activity.difficulties.find(
(candidate) => candidate.droppedItemLevel === selectedTierItemLevel, (candidate) => candidate.droppedItemLevel === selectedTierItemLevel,
) ?? activity.difficulties[0] ) ?? activity.difficulties[0]
const difficultyLocked = profile.character.level < selectedDifficulty.unlockLevel const difficultyLocked = profile.character.level < selectedDifficulty.unlockLevel
const sectionName = activity.contentType === 'raid' ? 'Phase' : 'Part'
const activityCompletionCount = activity.completionCount ?? 0
const marathonUnlocked = activityCompletionCount >= 10
const cloudSync = getCloudSyncStatus() const cloudSync = getCloudSyncStatus()
const canShowCloudSync = account.id !== -1 && cloudSync.available const canShowCloudSync = account.id !== -1 && cloudSync.available
const lootPreviewEncounters = [...activity.encounters] const lootPreviewEncounters = [...activity.encounters]
@@ -612,11 +607,6 @@ function App() {
{(screen === 'dungeons' || screen === 'raids') && ( {(screen === 'dungeons' || screen === 'raids') && (
<section className="content-screen dungeon-run-screen"> <section className="content-screen dungeon-run-screen">
<ScreenHeading
eyebrow="Adventure"
title={activity.contentType === 'raid' ? 'Raids' : 'Dungeons'}
onBack={() => setScreen('menu')}
/>
<div className="dungeon-run-board"> <div className="dungeon-run-board">
<div className="dungeon-run-main"> <div className="dungeon-run-main">
<article className="run-summary-card dungeon-focus-card"> <article className="run-summary-card dungeon-focus-card">
@@ -625,7 +615,10 @@ function App() {
</div> </div>
<div className="run-summary-copy"> <div className="run-summary-copy">
<p className="eyebrow">Selected Run</p> <p className="eyebrow">Selected Run</p>
<div className="run-title-row">
<h2>{activity.name}</h2> <h2>{activity.name}</h2>
<button className="back-button inline-back-button" onClick={() => setScreen('menu')} type="button">Back</button>
</div>
<p>{activity.description}</p> <p>{activity.description}</p>
<div className="tag-row"> <div className="tag-row">
<span>Level {activity.recommendedLevel}</span> <span>Level {activity.recommendedLevel}</span>
@@ -652,7 +645,7 @@ function App() {
> >
Prev Prev
</button> </button>
<span>{currentActivityPage + 1}/{activityPageCount}</span> <span>{activityPageStart}-{activityPageEnd} of {activityOptions.length}</span>
<button <button
disabled={currentActivityPage >= activityPageCount - 1} disabled={currentActivityPage >= activityPageCount - 1}
onClick={() => setActivityPage((page) => Math.min(activityPageCount - 1, page + 1))} onClick={() => setActivityPage((page) => Math.min(activityPageCount - 1, page + 1))}
@@ -754,9 +747,7 @@ function App() {
<small> <small>
{difficultyLocked {difficultyLocked
? `Unlocks at level ${selectedDifficulty.unlockLevel}` ? `Unlocks at level ${selectedDifficulty.unlockLevel}`
: marathonUnlocked : 'Marathon keeps health and mana between boss kills.'}
? 'Marathon keeps health and mana between boss kills.'
: `Marathon unlocks after 10 clears (${activityCompletionCount}/10).`}
</small> </small>
</div> </div>
<div className="part-picker"> <div className="part-picker">
@@ -764,7 +755,6 @@ function App() {
className="primary-button selected-part" className="primary-button selected-part"
disabled={difficultyLocked} disabled={difficultyLocked}
onClick={() => { onClick={() => {
setSelectedPart(1)
setSelectedMarathonMode(false) setSelectedMarathonMode(false)
setCombatContentId(activity.id) setCombatContentId(activity.id)
setSelectedDifficultyId(selectedDifficulty.id) setSelectedDifficultyId(selectedDifficulty.id)
@@ -775,10 +765,9 @@ function App() {
Start Hunt Start Hunt
</button> </button>
<button <button
className={`primary-button ${selectedMarathonMode ? 'selected-part' : ''} ${!marathonUnlocked ? 'locked' : ''}`} className={`primary-button ${selectedMarathonMode ? 'selected-part' : ''}`}
disabled={difficultyLocked || !marathonUnlocked} disabled={difficultyLocked}
onClick={() => { onClick={() => {
setSelectedPart(1)
setSelectedMarathonMode(true) setSelectedMarathonMode(true)
setCombatContentId(activity.id) setCombatContentId(activity.id)
setSelectedDifficultyId(selectedDifficulty.id) setSelectedDifficultyId(selectedDifficulty.id)
@@ -900,10 +889,10 @@ function App() {
</p> </p>
<div className="leaderboard-tabs"> <div className="leaderboard-tabs">
{([ {([
{ key: 'part_1', label: `${sectionName} 1` }, { key: 'part_1', label: 'Run' },
{ key: 'part_2', label: `${sectionName} 2` }, { key: 'part_2', label: 'Legacy 2' },
{ key: 'part_3', label: `${sectionName} 3` }, { key: 'part_3', label: 'Legacy 3' },
{ key: 'full_run', label: 'Full Run' }, { key: 'full_run', label: 'Legacy Full' },
] as const).map((tab) => ( ] as const).map((tab) => (
<button <button
key={tab.key} key={tab.key}
+5 -5
View File
@@ -402,7 +402,7 @@ export function CombatScreen({
})), })),
[dungeon.partySize, profile.character.name], [dungeon.partySize, profile.character.name],
) )
const sectionName = isRoguelike ? 'Stage' : dungeon.contentType === 'raid' ? 'Phase' : 'Part' const sectionName = isRoguelike ? 'Stage' : 'Run'
const contentName = isRoguelike ? 'Roguelike' : dungeon.contentType === 'raid' ? 'Raid' : 'Dungeon' const contentName = isRoguelike ? 'Roguelike' : dungeon.contentType === 'raid' ? 'Raid' : 'Dungeon'
const initialEncounterIndex = (startPart - 1) * 3 const initialEncounterIndex = (startPart - 1) * 3
const enemyCount = hardMode ? 2 : 1 const enemyCount = hardMode ? 2 : 1
@@ -1365,7 +1365,7 @@ export function CombatScreen({
}).reverse() }).reverse()
const dualScreenState = useMemo<DualScreenCombatState>(() => ({ const dualScreenState = useMemo<DualScreenCombatState>(() => ({
difficultyName: difficulty.name, difficultyName: difficulty.name,
dungeonName: hardMode ? `${dungeon.name} Hard` : dungeon.name, dungeonName: dungeon.name,
contentName, contentName,
encounterName: encounter.enemyName, encounterName: encounter.enemyName,
encounterDescription: encounter.description, encounterDescription: encounter.description,
@@ -1444,7 +1444,7 @@ export function CombatScreen({
> >
{!dualScreenEnabled && <header className="topbar"> {!dualScreenEnabled && <header className="topbar">
<div> <div>
<p className="eyebrow">{difficulty.name}{hardMode ? ' Hard' : ''} - Item Level {difficulty.droppedItemLevel}</p> <p className="eyebrow">{difficulty.name} - Item Level {difficulty.droppedItemLevel}</p>
<h1>{dungeon.name}</h1> <h1>{dungeon.name}</h1>
</div> </div>
<div className="combat-header-actions"> <div className="combat-header-actions">
@@ -1467,7 +1467,7 @@ export function CombatScreen({
</div> </div>
<div className="enemy-info"> <div className="enemy-info">
<div className="bar-label"> <div className="bar-label">
<strong>{hardMode ? `${encounter.enemyName} x2` : encounter.enemyName}</strong> <strong>{encounter.enemyName}</strong>
<span>{Math.ceil(enemyHealth)} / {encounterMaxHealth}</span> <span>{Math.ceil(enemyHealth)} / {encounterMaxHealth}</span>
</div> </div>
{hardMode ? ( {hardMode ? (
@@ -1796,7 +1796,7 @@ export function CombatScreen({
<div> <div>
<p className="eyebrow">{sectionName} Complete</p> <p className="eyebrow">{sectionName} Complete</p>
<h2>{encounter.enemyName} Defeated</h2> <h2>{encounter.enemyName} Defeated</h2>
<p>{canContinueAfterPart ? `Proceed to ${sectionName} ${currentPart + 1} or end the run?` : 'Hard mode for this section is complete.'}</p> <p>{canContinueAfterPart ? `Proceed to ${sectionName} ${currentPart + 1} or end the run?` : 'Run checkpoint complete.'}</p>
{canContinueAfterPart && ( {canContinueAfterPart && (
<button <button
onClick={() => { onClick={() => {