Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Lückemeyer
VoCoach
Commits
044bb284
Commit
044bb284
authored
Apr 18, 2026
by
Lückemeyer
Browse files
Initial commit of claude ai assisted vocab trainer android app
parent
eab02a57
Changes
118
Hide whitespace changes
Inline
Side-by-side
app/src/main/assets/icon_bg.svg
0 → 100644
View file @
044bb284
<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>
app/src/main/assets/icon_fg.svg
0 → 100644
View file @
044bb284
<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>
app/src/main/ic_launcher-playstore.png
0 → 100644
View file @
044bb284
14.5 KB
app/src/main/java/com/example/myfirstapp/MainActivity.java
deleted
100644 → 0
View file @
eab02a57
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
);
}
}
app/src/main/java/dev/lueckemeyer/vocoach/MainActivity.java
0 → 100644
View file @
044bb284
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
);
}
}
app/src/main/java/dev/lueckemeyer/vocoach/VoCoachApp.java
0 → 100644
View file @
044bb284
package
dev.lueckemeyer.vocoach
;
import
android.app.Application
;
public
class
VoCoachApp
extends
Application
{
// Entry point — Room database is initialised lazily on first access.
}
app/src/main/java/dev/lueckemeyer/vocoach/db/VocabDatabase.java
0 → 100644
View file @
044bb284
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
;
}
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/BookDao.java
0 → 100644
View file @
044bb284
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
();
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/CardDao.java
0 → 100644
View file @
044bb284
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
);
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/DetailDao.java
0 → 100644
View file @
044bb284
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
);
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/LeitnerStateDao.java
0 → 100644
View file @
044bb284
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
);
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/SessionDao.java
0 → 100644
View file @
044bb284
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
);
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/TryDao.java
0 → 100644
View file @
044bb284
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
;
}
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/UnitDao.java
0 → 100644
View file @
044bb284
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
);
}
app/src/main/java/dev/lueckemeyer/vocoach/db/dao/VoletDao.java
0 → 100644
View file @
044bb284
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
();
}
app/src/main/java/dev/lueckemeyer/vocoach/db/entity/AdjectiveDetail.java
0 → 100644
View file @
044bb284
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
;
}
app/src/main/java/dev/lueckemeyer/vocoach/db/entity/AdverbDetail.java
0 → 100644
View file @
044bb284
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
;
}
app/src/main/java/dev/lueckemeyer/vocoach/db/entity/Book.java
0 → 100644
View file @
044bb284
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
;
}
app/src/main/java/dev/lueckemeyer/vocoach/db/entity/Card.java
0 → 100644
View file @
044bb284
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
;
}
app/src/main/java/dev/lueckemeyer/vocoach/db/entity/LeitnerState.java
0 → 100644
View file @
044bb284
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
;
}
Prev
1
2
3
4
5
6
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment