Commit 044bb284 authored by Lückemeyer's avatar Lückemeyer
Browse files

Initial commit of claude ai assisted vocab trainer android app

parent eab02a57
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="108" height="108" fill="#3F51B5"/>
</svg>
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M54 30C41.85 30 32 38.95 32 50C32 55.41 34.42 60.31 38.35 63.85L36 74L47.5 68.25C49.54 69.38 51.7 70 54 70C66.15 70 76 61.05 76 50C76 38.95 66.15 30 54 30ZM48 58L40 50L42.83 47.17L48 52.34L61.17 39.17L64 42L48 58Z" fill="white"/>
</svg>
package com.example.myfirstapp;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
package dev.lueckemeyer.vocoach;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import dev.lueckemeyer.vocoach.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
// Home is top-level — no back arrow there; all others get one.
AppBarConfiguration appBarConfig = new AppBarConfiguration.Builder(R.id.homeFragment).build();
NavigationUI.setupWithNavController(binding.toolbar, navController, appBarConfig);
}
}
package dev.lueckemeyer.vocoach;
import android.app.Application;
public class VoCoachApp extends Application {
// Entry point — Room database is initialised lazily on first access.
}
package dev.lueckemeyer.vocoach.db;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import dev.lueckemeyer.vocoach.db.dao.*;
import dev.lueckemeyer.vocoach.db.entity.*;
@Database(entities = {Book.class, Unit.class, Volet.class, Card.class,
NounDetail.class, VerbDetail.class, AdjectiveDetail.class, AdverbDetail.class,
LeitnerState.class, Session.class, TryRecord.class},
version = 1, exportSchema = true)
public abstract class VocabDatabase extends RoomDatabase {
private static volatile VocabDatabase INSTANCE;
public abstract BookDao bookDao();
public abstract UnitDao unitDao();
public abstract VoletDao voletDao();
public abstract CardDao cardDao();
public abstract DetailDao detailDao();
public abstract LeitnerStateDao leitnerStateDao();
public abstract SessionDao sessionDao();
public abstract TryDao tryDao();
public static VocabDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (VocabDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
VocabDatabase.class, "vocab.db")
.createFromAsset("corpus.db")
.build();
}
}
}
return INSTANCE;
}
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
import dev.lueckemeyer.vocoach.db.entity.Book;
@Dao
public interface BookDao {
@Insert long insert(Book book);
@Query("SELECT * FROM book ORDER BY id ASC") List<Book> getAllSync();
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
import dev.lueckemeyer.vocoach.db.entity.Card;
@Dao
public interface CardDao {
@Insert long insert(Card card);
@Query("SELECT * FROM card WHERE id = :id LIMIT 1") Card getByIdSync(int id);
@Query("SELECT * FROM card WHERE volet_id = :voletId ORDER BY id ASC") List<Card> getByVoletSync(int voletId);
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import dev.lueckemeyer.vocoach.db.entity.AdjectiveDetail;
import dev.lueckemeyer.vocoach.db.entity.AdverbDetail;
import dev.lueckemeyer.vocoach.db.entity.NounDetail;
import dev.lueckemeyer.vocoach.db.entity.VerbDetail;
@Dao
public interface DetailDao {
@Insert void insertNoun(NounDetail d);
@Insert void insertVerb(VerbDetail d);
@Insert void insertAdjective(AdjectiveDetail d);
@Insert void insertAdverb(AdverbDetail d);
@Query("SELECT * FROM noun_detail WHERE card_id = :cardId LIMIT 1")
NounDetail getNounSync(int cardId);
@Query("SELECT * FROM verb_detail WHERE card_id = :cardId LIMIT 1")
VerbDetail getVerbSync(int cardId);
@Query("SELECT * FROM adjective_detail WHERE card_id = :cardId LIMIT 1")
AdjectiveDetail getAdjectiveSync(int cardId);
@Query("SELECT * FROM adverb_detail WHERE card_id = :cardId LIMIT 1")
AdverbDetail getAdverbSync(int cardId);
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;
import dev.lueckemeyer.vocoach.db.entity.LeitnerState;
@Dao
public interface LeitnerStateDao {
@Insert long insert(LeitnerState state);
@Update void update(LeitnerState state);
@Query("SELECT * FROM leitner_state WHERE id = :id LIMIT 1")
LeitnerState getByIdSync(int id);
@Query("SELECT * FROM leitner_state WHERE card_id = :cardId AND direction = :direction LIMIT 1")
LeitnerState getSync(int cardId, String direction);
@Query("SELECT ls.* FROM leitner_state ls " +
"JOIN card c ON c.id = ls.card_id " +
"JOIN volet v ON v.id = c.volet_id " +
"JOIN unit u ON u.id = v.unit_id " +
"WHERE ls.direction = :direction AND ls.box_number = :box " +
"ORDER BY u.book_id ASC, u.position ASC, v.position ASC")
List<LeitnerState> getByBoxOrderedSync(String direction, int box);
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import dev.lueckemeyer.vocoach.db.entity.Session;
@Dao
public interface SessionDao {
@Insert long insert(Session session);
@Update void update(Session session);
@Query("SELECT * FROM session WHERE id = :id LIMIT 1")
Session getByIdSync(int id);
@Query("SELECT * FROM session WHERE mode = :mode ORDER BY started_at DESC LIMIT 1")
Session getLatestSync(String mode);
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
import dev.lueckemeyer.vocoach.db.entity.TryRecord;
@Dao
public interface TryDao {
@Insert void insert(TryRecord t);
@Query("SELECT * FROM try_record WHERE leitner_state_id = :stateId ORDER BY responded_at ASC")
List<TryRecord> getForStateSync(int stateId);
/** 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")
List<Integer> getAttemptedTodaySync(String direction, long dayStart);
// ── Stats queries ──────────────────────────────────────────────────────
/** Success rate per volet (all-time). */
@Query("SELECT v.id AS voletId, v.title AS voletTitle, " +
"u.title AS unitTitle, b.title AS bookTitle, " +
"COUNT(tr.id) AS total, SUM(tr.correct) AS correct " +
"FROM try_record tr " +
"JOIN leitner_state ls ON ls.id = tr.leitner_state_id " +
"JOIN card c ON c.id = ls.card_id " +
"JOIN volet v ON v.id = c.volet_id " +
"JOIN unit u ON u.id = v.unit_id " +
"JOIN book b ON b.id = u.book_id " +
"GROUP BY v.id " +
"ORDER BY (CAST(SUM(tr.correct) AS REAL) / COUNT(tr.id)) DESC")
List<VoletStat> getVoletStatsSync();
/** Success rate per calendar day. */
@Query("SELECT date(responded_at, 'unixepoch') AS day, " +
"COUNT(id) AS total, SUM(correct) AS correct " +
"FROM try_record " +
"GROUP BY day ORDER BY day DESC")
List<DayStat> getDayStatsSync();
class VoletStat {
public int voletId;
public String voletTitle;
public String unitTitle;
public String bookTitle;
public int total;
public int correct;
}
class DayStat {
public String day;
public int total;
public int correct;
}
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
import dev.lueckemeyer.vocoach.db.entity.Unit;
@Dao
public interface UnitDao {
@Insert long insert(Unit unit);
@Query("SELECT * FROM unit WHERE book_id = :bookId ORDER BY position ASC") List<Unit> getByBookSync(int bookId);
}
package dev.lueckemeyer.vocoach.db.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
import dev.lueckemeyer.vocoach.db.entity.Volet;
@Dao
public interface VoletDao {
@Insert long insert(Volet volet);
@Query("SELECT * FROM volet WHERE unit_id = :unitId ORDER BY position ASC") List<Volet> getByUnitSync(int unitId);
@Query("SELECT * FROM volet ORDER BY id ASC") List<Volet> getAllSync();
}
package dev.lueckemeyer.vocoach.db.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity(tableName = "adjective_detail",
foreignKeys = @ForeignKey(entity = Card.class, parentColumns = "id",
childColumns = "card_id", onDelete = ForeignKey.CASCADE),
indices = @Index(value = "card_id", unique = true))
public class AdjectiveDetail {
@PrimaryKey(autoGenerate = true) public int id;
@ColumnInfo(name = "card_id") public int cardId;
@ColumnInfo(name = "feminine_form") public String feminineForm;
@ColumnInfo(name = "plural_form") public String pluralForm;
@ColumnInfo(name = "feminine_plural_form") public String femininePluralForm;
}
package dev.lueckemeyer.vocoach.db.entity;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity(tableName = "adverb_detail",
foreignKeys = @ForeignKey(entity = Card.class, parentColumns = "id",
childColumns = "card_id", onDelete = ForeignKey.CASCADE),
indices = @Index(value = "card_id", unique = true))
public class AdverbDetail {
@PrimaryKey(autoGenerate = true) public int id;
@ColumnInfo(name = "card_id") public int cardId;
@ColumnInfo(name = "derived_from") public String derivedFrom;
}
package dev.lueckemeyer.vocoach.db.entity;
import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "book")
public class Book {
@PrimaryKey(autoGenerate = true)
public int id;
public String title;
public String description;
}
package dev.lueckemeyer.vocoach.db.entity;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity(tableName = "card",
foreignKeys = @ForeignKey(entity = Volet.class, parentColumns = "id",
childColumns = "volet_id", onDelete = ForeignKey.CASCADE),
indices = @Index("volet_id"))
public class Card {
@PrimaryKey(autoGenerate = true) public int id;
@ColumnInfo(name = "volet_id") public int voletId;
@NonNull
public String french;
@NonNull
@ColumnInfo(name = "native_lang") public String nativeLang;
public String phonetic;
@ColumnInfo(name = "word_type") public String wordType;
@ColumnInfo(name = "example_sentence") public String exampleSentence;
public String notes;
@ColumnInfo(name = "created_at") public long createdAt;
}
package dev.lueckemeyer.vocoach.db.entity;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
@Entity(tableName = "leitner_state",
foreignKeys = @ForeignKey(entity = Card.class, parentColumns = "id",
childColumns = "card_id", onDelete = ForeignKey.CASCADE),
indices = {@Index("card_id"),
@Index(value = {"card_id", "direction"}, unique = true)})
public class LeitnerState {
@PrimaryKey(autoGenerate = true) public int id;
@ColumnInfo(name = "card_id") public int cardId;
public String direction; // "fr_to_native" | "native_to_fr"
@ColumnInfo(name = "box_number") public int boxNumber;
@ColumnInfo(name = "next_review_at") public long nextReviewAt;
@ColumnInfo(name = "updated_at") public long updatedAt;
}
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