Commit b7ce3c35 authored by Lückemeyer's avatar Lückemeyer
Browse files

claude added streak, #iterations 1

parent 3e9ac10f
......@@ -17,6 +17,21 @@ public interface TryDao {
@Query("SELECT * FROM try_record ORDER BY responded_at ASC")
List<TryRecord> getAllSync();
/**
* Ordered correct/wrong sequence for a single session — used for
* session-streak and session-score calculation.
*/
@Query("SELECT correct FROM try_record WHERE session_id = :sessionId ORDER BY responded_at ASC")
List<Integer> getCorrectSequenceForSessionSync(int sessionId);
/**
* Most recent N try records overall (all sessions) for computing the
* current streak — ordered oldest-first so we can walk forward.
* Fetching 500 is more than enough for any realistic streak.
*/
@Query("SELECT correct FROM try_record ORDER BY responded_at DESC LIMIT 500")
List<Integer> getRecentCorrectSequenceSync();
/** Returns leitner_state_ids attempted today (epoch seconds window). */
@Query("SELECT DISTINCT leitner_state_id FROM try_record " +
"WHERE direction = :direction AND responded_at >= :dayStart")
......
......@@ -111,6 +111,80 @@ public class TrainingRepository {
public List<TryDao.VoletDayStat> getVoletDayStats() { return db.tryDao().getVoletDayStatsSync(); }
public List<TryDao.DayStat> getDayStats() { return db.tryDao().getDayStatsSync(); }
// ── Live streak counters (called from background thread) ──────────────────
/**
* Current streak: length of the unbroken run of correct answers at the
* very end of all-time history (most recent first → reverse scan).
*/
public int currentStreak() {
List<Integer> recent = db.tryDao().getRecentCorrectSequenceSync();
int streak = 0;
for (int v : recent) {
if (v == 1) streak++;
else break;
}
return streak;
}
/** Longest streak within the given session. */
public int sessionLongestStreak(int sessionId) {
return longestStreak(db.tryDao().getCorrectSequenceForSessionSync(sessionId));
}
/** All-time longest streak across every session. */
public int overallLongestStreak() {
// getAllSync is ordered ASC by responded_at
List<TryRecord> all = db.tryDao().getAllSync();
List<Integer> seq = new java.util.ArrayList<>(all.size());
for (TryRecord tr : all) seq.add(tr.correct ? 1 : 0);
return longestStreak(seq);
}
/**
* Session score — gamification formula (see design notes in TrainingFragment).
*
* Score = Σ over each correct answer of: basePoints × streakMultiplier × boxMultiplier
*
* basePoints = 10
* streakMultiplier = 1.0 + 0.1 × min(streakLengthAtThatPoint, 10) (max ×2.0)
* boxMultiplier = 0.5 + 0.5 × boxNumber (box1=1.0 … box5=3.0)
*
* Wrong answers break the streak multiplier but do not subtract points.
* Retiring a card (box 6) awards a one-time bonus of 50 points.
*/
public int sessionScore(int sessionId) {
List<TryRecord> tries = db.tryDao().getAllSync();
// filter to session
java.util.List<TryRecord> session = new java.util.ArrayList<>();
for (TryRecord tr : tries) {
if (tr.sessionId != null && tr.sessionId == sessionId) session.add(tr);
}
int score = 0, streak = 0;
for (TryRecord tr : session) {
if (tr.correct) {
streak++;
// Determine current box of the leitner state at time of answer
dev.lueckemeyer.vocoach.db.entity.LeitnerState ls =
db.leitnerStateDao().getByIdSync(tr.leitnerStateId);
int box = (ls != null) ? ls.boxNumber : 1;
double streakMult = 1.0 + 0.1 * Math.min(streak, 10);
double boxMult = 0.5 + 0.5 * box;
score += (int) (10 * streakMult * boxMult);
if (box == 6) score += 50; // retirement bonus
} else {
streak = 0;
}
}
return score;
}
private static int longestStreak(List<Integer> seq) {
int max = 0, cur = 0;
for (int v : seq) { if (v == 1) { cur++; max = Math.max(max, cur); } else cur = 0; }
return max;
}
// ── Helpers ───────────────────────────────────────────────────────────────
private TrainingCard buildCard(LeitnerState ls) {
......
......@@ -25,6 +25,12 @@ public class TrainingFragment extends Fragment {
private TrainingCard current;
private final ExecutorService exec = Executors.newSingleThreadExecutor();
// ── Live counters (updated on background thread, read on UI thread) ───────
private volatile int currentStreak = 0; // unbroken run ending right now
private volatile int sessionBest = 0; // longest streak this session
private volatile int overallBest = 0; // longest streak all time
private volatile int sessionScore = 0; // gamification score this session
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
......@@ -43,6 +49,11 @@ public class TrainingFragment extends Fragment {
exec.execute(() -> {
sessionId = repo.getOrCreateSession(direction);
// Pre-load streak baselines from DB
currentStreak = repo.currentStreak();
sessionBest = repo.sessionLongestStreak(sessionId);
overallBest = repo.overallLongestStreak();
sessionScore = repo.sessionScore(sessionId);
loadNextCard();
});
......@@ -82,6 +93,7 @@ public class TrainingFragment extends Fragment {
boolean frToNative = "fr_to_native".equals(direction);
binding.tvBox.setText("Box " + tc.leitnerState.boxNumber);
updateStreakBar();
binding.tvPromptLabel.setText(frToNative ? "French" : "German");
binding.tvPrompt.setText(frToNative ? card.french : card.nativeLang);
binding.tvAnswerLabel.setText(frToNative ? "German" : "French");
......@@ -92,10 +104,15 @@ public class TrainingFragment extends Fragment {
binding.btnSubmit.setVisibility(View.VISIBLE);
// Word-type panels (only shown when answering in French direction — grammar is French)
boolean showGrammar = frToNative;
boolean showGrammar = !frToNative;
setupWordTypeFields(tc, showGrammar);
}
private void updateStreakBar() {
binding.tvStreaks.setText("🔥 " + currentStreak + "/" + sessionBest + "/" + overallBest);
binding.tvScore.setText("⭐ " + sessionScore + " pts");
}
private void setupWordTypeFields(TrainingCard tc, boolean showGrammar) {
binding.panelNoun.setVisibility(View.GONE);
binding.panelVerb.setVisibility(View.GONE);
......@@ -177,6 +194,14 @@ public class TrainingFragment extends Fragment {
repo.recordResult(current.leitnerState, finalCorrect, sessionId);
repo.clearPending(direction);
repo.touchSession(sessionId);
// Recalculate all counters on the background thread
currentStreak = repo.currentStreak();
sessionBest = repo.sessionLongestStreak(sessionId);
overallBest = repo.overallLongestStreak();
sessionScore = repo.sessionScore(sessionId);
requireActivity().runOnUiThread(this::updateStreakBar);
});
}
......
......@@ -9,14 +9,49 @@
android:orientation="vertical"
android:padding="20dp">
<!-- Box indicator -->
<TextView
android:id="@+id/tv_box"
android:layout_width="wrap_content"
<!-- Top bar: box indicator (left) + streak / score (right) -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceCaption"
android:textColor="?attr/colorPrimary"
android:layout_marginBottom="8dp"/>
android:layout_marginBottom="8dp">
<TextView
android:id="@+id/tv_box"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:textAppearance="?attr/textAppearanceCaption"
android:textColor="?attr/colorPrimary"/>
<LinearLayout
android:id="@+id/layout_streaks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:orientation="vertical"
android:gravity="end">
<!-- 🔥 current / session / overall -->
<TextView
android:id="@+id/tv_streaks"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceCaption"
android:textColor="?attr/colorPrimary"
android:text="🔥 –/–/–"/>
<!-- ⭐ session score -->
<TextView
android:id="@+id/tv_score"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceCaption"
android:text="⭐ 0 pts"/>
</LinearLayout>
</RelativeLayout>
<!-- Prompt -->
<TextView
......
......@@ -650,7 +650,7 @@ code + .copy-button {
<script type="text/javascript">
function configurationCacheProblems() { return (
// begin-report-data
{"diagnostics":[{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('namespace = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('compileSdk = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('minSdk = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('targetSdk = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('viewBinding = <value>') instead."}]]},{"locations":[{"pluginId":"com.android.internal.application"}],"problem":[{"text":"The StartParameter.isConfigurationCacheRequested property has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"The StartParameter.isConfigurationCacheRequested property has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#deprecated_startparameter_is_configuration_cache_requested","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"the-startparameter-isconfigurationcacherequested-property-has-been-deprecated","displayName":"The StartParameter.isConfigurationCacheRequested property has been deprecated."}],"solutions":[[{"text":"Please use 'configurationCache.requested' property on 'BuildFeatures' service instead."}]]}],"problemsReport":{"totalProblemCount":6,"buildName":"VoCoach","requestedTasks":":app:assembleDebug :app:assembleDebugUnitTest :app:assembleDebugAndroidTest","documentationLink":"https://docs.gradle.org/9.0.0/userguide/reporting_problems.html","documentationLinkCaption":"Problem report","summaries":[]}}
{"diagnostics":[{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('namespace = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('compileSdk = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('minSdk = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('targetSdk = <value>') instead."}]]},{"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('viewBinding = <value>') instead."}]]},{"locations":[{"pluginId":"com.android.internal.application"}],"problem":[{"text":"The StartParameter.isConfigurationCacheRequested property has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"The StartParameter.isConfigurationCacheRequested property has been deprecated.","documentationLink":"https://docs.gradle.org/9.0.0/userguide/upgrading_version_8.html#deprecated_startparameter_is_configuration_cache_requested","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"the-startparameter-isconfigurationcacherequested-property-has-been-deprecated","displayName":"The StartParameter.isConfigurationCacheRequested property has been deprecated."}],"solutions":[[{"text":"Please use 'configurationCache.requested' property on 'BuildFeatures' service instead."}]]}],"problemsReport":{"totalProblemCount":6,"buildName":"VoCoach","requestedTasks":":app:assembleDebug","documentationLink":"https://docs.gradle.org/9.0.0/userguide/reporting_problems.html","documentationLinkCaption":"Problem report","summaries":[]}}
// end-report-data
);}
</script>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment