Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ local.properties



/docs/
/docs/
1 change: 1 addition & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/markdown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 109 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Development Commands

```bash
# Build
./gradlew build
./gradlew assembleRelease
./gradlew assembleDebug

# Install debug build to connected device
./gradlew installDebug

# Testing
./gradlew test # Unit tests
./gradlew connectedAndroidTest # Instrumented tests (requires device/emulator)

# Code quality
./gradlew lint

# Backend (api-backend/)
npm run build # Compile TypeScript
npm run deploy # Deploy to Firebase Functions (asia-south1)
```

## Architecture Overview

**MVVM + Repository pattern** throughout the app.

### Android App (`app/`)

```
com.kharagedition.tibetankeyboard/
├── app/ # Application class (RevenueCat, Firebase init)
├── service/ # TibetanKeyboard.kt — the IME InputMethodService
├── ui/ # Activities + ViewModels (one ViewModel per screen)
│ ├── keyboard/ # TibetanKeyboardView, AIKeyboardView, EmojiKeyboardView
│ ├── grammar/ # GrammarActivity + GrammarViewModel
│ ├── transliteration/
│ ├── chat/ # ChatActivity with history, document upload, tutoring mode
│ ├── home/, settings/, login/, splash/, subscription/
├── data/
│ ├── repository/ # AIRepository (central API hub), ChatRepository, UserRepository
│ ├── remote/ # RetrofitClient singleton + API interface definitions
│ ├── local/ # UserPreferences (SharedPreferences wrapper)
│ └── model/ # Data classes
├── auth/ # AuthManager (Firebase Auth)
└── util/ # Extensions, PerformanceOptimizer (LRU cache + debounce)
```

### Backend (`api-backend/`)

Node.js/TypeScript Express app deployed as Firebase Cloud Functions.

- **AI**: Google Gemini 2.0 Flash (grammar, chat)
- **Translation**: Google Translate API
- **Auth middleware**: API key validation + per-user usage limits enforced in Firestore

Key endpoints: `POST /api/grammar/analyze`, `POST /api/transliterate/convert`, `POST /chat`, `POST /translate`

### IME Service Flow

`TibetanKeyboard` (InputMethodService) creates the keyboard UI:
- `AIKeyboardView` — top toolbar with AI feature buttons
- `TibetanKeyboardView` — the actual typing surface (Tibetan Uchen or QWERTY)
- `KeyboardMode` enum: `NORMAL`, `AI_GRAMMAR`, `AI_REPHRASE`

Grammar/transliteration/chat features open as separate Activities launched from the toolbar.

### Data Flow

```
Activity → ViewModel.someAction()
→ Repository.apiCall() [Coroutine on IO]
→ Retrofit → Backend API
→ _liveData.value = result
→ Activity observer → UI update
```

### Premium Feature Gating

`PremiumFeatureManager` + `RevenueCatManager` control access. Free tier has daily usage limits stored in Firestore:
- Grammar: 10/day free
- Transliteration: 20/day free
- Chat: 100 messages free

### Key Libraries

| Purpose | Library |
|---------|---------|
| HTTP | Retrofit 2 + OkHttp3 |
| JSON | Kotlinx Serialization 1.6.3 |
| Async | Kotlin Coroutines 1.8.1 |
| Images | Glide 4.16.0 |
| Animations | Lottie 6.6.2 |
| Subscriptions | RevenueCat 9.2.0 |
| Ads | AdMob 23.6.0 |
| Backend AI | Gemini 2.0 Flash via `@google/generative-ai` |

### Build Configuration

- **App ID**: `com.kharagedition.tibetankeyboard`
- **Min SDK**: 23 (Android 6.0)
- **Target/Compile SDK**: 36
- **Kotlin**: 2.0.0, **Java**: 17
- Release builds use ProGuard + resource shrinking; `app/lint-baseline.xml` suppresses known lint issues
- View Binding is enabled
Empty file added TEST_CMD.md
Empty file.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,5 @@ dependencies {


implementation 'com.revenuecat.purchases:purchases:9.2.0'
implementation project(':botok')
}
18 changes: 9 additions & 9 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


<application
android:name=".TibetanKeyboardApp"
android:name=".app.TibetanKeyboardApp"
android:allowBackup="true"
android:hardwareAccelerated="true"
android:icon="@mipmap/ic_launcher"
Expand All @@ -20,27 +20,27 @@
android:supportsRtl="true"
android:theme="@style/Theme.TibetanKeyboard">
<service
android:name=".MyFirebaseMessagingService"
android:name=".service.MyFirebaseMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<activity
android:name=".LoginActivity"
android:name=".ui.login.LoginActivity"
android:exported="false" />
<activity
android:name=".ui.ChatActivity"
android:name=".ui.chat.ChatActivity"
android:exported="false"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".SettingsActivity"
android:name=".ui.settings.SettingsActivity"
android:exported="true"
android:label="@string/title_activity_settings"
android:parentActivityName=".ui.HomeActivity" />
android:parentActivityName=".ui.home.HomeActivity" />

<service
android:name=".TibetanKeyboard"
android:name=".service.TibetanKeyboard"
android:exported="true"
android:label="Tibetan Keyboard"
android:permission="android.permission.BIND_INPUT_METHOD">
Expand All @@ -54,7 +54,7 @@
</service>

<activity
android:name=".ui.SplashScreenActivity"
android:name=".ui.splash.SplashScreenActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -63,7 +63,7 @@
</intent-filter>
</activity>
<activity
android:name=".ui.HomeActivity"
android:name=".ui.home.HomeActivity"
android:hardwareAccelerated="true" />

<meta-data
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.application
package com.kharagedition.tibetankeyboard.app

import android.os.Bundle
import android.view.inputmethod.InputMethodManager
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard
package com.kharagedition.tibetankeyboard.app

import android.app.Application
import android.content.SharedPreferences
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import com.kharagedition.tibetankeyboard.LoginActivity
import com.kharagedition.tibetankeyboard.UserPreferences
import com.kharagedition.tibetankeyboard.subscription.RevenueCatManager
import com.kharagedition.tibetankeyboard.ui.login.LoginActivity
import com.kharagedition.tibetankeyboard.data.local.UserPreferences
import com.kharagedition.tibetankeyboard.data.repository.RevenueCatManager

/**
* Manages authentication state and user session
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard
package com.kharagedition.tibetankeyboard.data.local

import android.content.Context
import android.content.SharedPreferences
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.chat
package com.kharagedition.tibetankeyboard.data.model

import java.util.Date

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.model
package com.kharagedition.tibetankeyboard.data.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.util
package com.kharagedition.tibetankeyboard.data.model

data class User(
val uid: String = "",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.ai
package com.kharagedition.tibetankeyboard.data.remote

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.kharagedition.tibetankeyboard.data.remote

import com.kharagedition.tibetankeyboard.data.model.GrammarCheckRequest
import com.kharagedition.tibetankeyboard.data.model.GrammarCheckResponse
import com.kharagedition.tibetankeyboard.data.model.TranslationRequest
import com.kharagedition.tibetankeyboard.data.model.TranslationResponse
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface YigChikAIAPI {
@Headers("Content-Type: application/json", "Accept: application/json")
@POST("grammar-check")
suspend fun checkGrammar(@Body request: GrammarCheckRequest): GrammarCheckResponse
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.kharagedition.tibetankeyboard.ai
package com.kharagedition.tibetankeyboard.data.remote

import com.kharagedition.tibetankeyboard.model.GeminiChatRequest
import com.kharagedition.tibetankeyboard.model.GeminiChatResponse
import com.kharagedition.tibetankeyboard.model.GrammarCheckRequest
import com.kharagedition.tibetankeyboard.model.GrammarCheckResponse
import com.kharagedition.tibetankeyboard.data.model.GeminiChatRequest
import com.kharagedition.tibetankeyboard.data.model.GeminiChatResponse
import com.kharagedition.tibetankeyboard.data.model.GrammarCheckRequest
import com.kharagedition.tibetankeyboard.data.model.GrammarCheckResponse
import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.Headers
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.kharagedition.tibetankeyboard.ai
package com.kharagedition.tibetankeyboard.data.remote

import com.kharagedition.tibetankeyboard.model.TranslationRequest
import com.kharagedition.tibetankeyboard.model.TranslationResponse
import com.kharagedition.tibetankeyboard.data.model.TranslationRequest
import com.kharagedition.tibetankeyboard.data.model.TranslationResponse
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.ai
package com.kharagedition.tibetankeyboard.data.repository

import android.app.Application
import android.content.Context
Expand All @@ -10,6 +10,9 @@ import java.net.HttpURLConnection
import java.net.URL
import java.util.UUID
import org.json.JSONObject
import com.kharagedition.tibetankeyboard.ui.grammar.GrammarAnalysisResult
import com.kharagedition.tibetankeyboard.ui.grammar.GrammarCorrection
import com.kharagedition.tibetankeyboard.data.remote.RetrofitClient

/**
* Repository for AI feature API calls
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.kharagedition.tibetankeyboard.ai

import com.kharagedition.tibetankeyboard.model.GrammarCheckRequest
import com.kharagedition.tibetankeyboard.model.GrammarResult
import com.kharagedition.tibetankeyboard.model.RephraseResult
import com.kharagedition.tibetankeyboard.model.TranslationRequest
import com.kharagedition.tibetankeyboard.model.TranslationResult
package com.kharagedition.tibetankeyboard.data.repository

import com.kharagedition.tibetankeyboard.data.model.GrammarCheckRequest
import com.kharagedition.tibetankeyboard.data.model.GrammarResult
import com.kharagedition.tibetankeyboard.data.model.RephraseResult
import com.kharagedition.tibetankeyboard.data.model.TranslationRequest
import com.kharagedition.tibetankeyboard.data.model.TranslationResult
import com.kharagedition.tibetankeyboard.data.remote.RetrofitClient
import kotlinx.coroutines.delay
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.kharagedition.tibetankeyboard.chat
package com.kharagedition.tibetankeyboard.data.repository

import com.kharagedition.tibetankeyboard.ai.RetrofitClient
import com.kharagedition.tibetankeyboard.model.GeminiChatRequest
import com.kharagedition.tibetankeyboard.data.remote.RetrofitClient
import com.kharagedition.tibetankeyboard.data.model.GeminiChatRequest

class ChatRepository() {
private var currentSessionId: String? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.kharagedition.tibetankeyboard.ai
package com.kharagedition.tibetankeyboard.data.repository

import android.app.Activity
import android.content.Context
import com.kharagedition.tibetankeyboard.subscription.RevenueCatManager
import com.kharagedition.tibetankeyboard.data.repository.RevenueCatManager
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
Expand Down Expand Up @@ -103,7 +103,7 @@ class PremiumFeatureManager(private val context: Context) {
.setMessage(message)
.setIcon(android.R.drawable.ic_dialog_info)
.setPositiveButton("Upgrade") { _, _ ->
revenueCatManager.purchasePremium(activity, object : com.kharagedition.tibetankeyboard.subscription.RevenueCatManager.SubscriptionCallback {
revenueCatManager.purchasePremium(activity, object : com.kharagedition.tibetankeyboard.data.repository.RevenueCatManager.SubscriptionCallback {
override fun onSuccess(message: String) {
onPurchase?.invoke()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.kharagedition.tibetankeyboard.subscription
package com.kharagedition.tibetankeyboard.data.repository

import android.app.Activity
import android.content.Context
Expand Down
Loading
Loading