Sriverse
File Tree Structure¶
app/
├── src/main/
│ ├── java/com/versepad/
│ │ ├── MainActivity.java
│ │ ├── models/
│ │ │ ├── Poem.java
│ │ │ ├── WordAnalysis.java
│ │ │ └── RhymeData.java
│ │ ├── services/
│ │ │ ├── DictionaryService.java
│ │ │ ├── RhymeService.java
│ │ │ ├── MeterService.java
│ │ │ ├── PhoneticsService.java
│ │ │ └── ThesaurusService.java
│ │ ├── adapters/
│ │ │ ├── SuggestionAdapter.java
│ │ │ └── PoemListAdapter.java
│ │ ├── fragments/
│ │ │ ├── EditorFragment.java
│ │ │ └── AnalysisFragment.java
│ │ └── utils/
│ │ ├── DatabaseHelper.java
│ │ └── FileUtils.java
│ ├── res/
│ │ ├── layout/
│ │ │ ├── activity_main.xml
│ │ │ ├── fragment_editor.xml
│ │ │ ├── fragment_analysis.xml
│ │ │ ├── item_suggestion.xml
│ │ │ └── item_poem.xml
│ │ ├── values/
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── drawable/
│ │ │ └── ic_launcher.xml
│ │ └── menu/
│ │ └── main_menu.xml
│ ├── assets/
│ │ ├── cmudict.txt
│ │ ├── dictionary.txt
│ │ └── thesaurus.txt
│ └── AndroidManifest.xml
├── build.gradle (Module: app)
└── build.gradle (Project)
1. build.gradle (Project level)¶
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.0'
}
}
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
2. build.gradle (Module: app)¶
plugins {
id 'com.android.application'
}
android {
namespace 'com.versepad'
compileSdk 34
defaultConfig {
applicationId "com.versepad"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.fragment:fragment:1.6.1'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
// Text processing libraries
implementation 'com.github.takscape:java-cmu-sphinx:1.0.0'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-text:1.10.0'
// JSON processing
implementation 'com.google.code.gson:gson:2.10.1'
// Database
implementation 'androidx.room:room-runtime:2.5.0'
annotationProcessor 'androidx.room:room-compiler:2.5.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
3. AndroidManifest.xml¶
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
4. MainActivity.java¶
package com.versepad;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.versepad.fragments.EditorFragment;
import com.versepad.fragments.AnalysisFragment;
import com.versepad.services.*;
public class MainActivity extends AppCompatActivity {
private DictionaryService dictionaryService;
private RhymeService rhymeService;
private MeterService meterService;
private PhoneticsService phoneticsService;
private ThesaurusService thesaurusService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize services
initializeServices();
// Setup bottom navigation
BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation);
bottomNav.setOnItemSelectedListener(item -> {
Fragment selectedFragment = null;
if (item.getItemId() == R.id.nav_editor) {
selectedFragment = new EditorFragment();
} else if (item.getItemId() == R.id.nav_analysis) {
selectedFragment = new AnalysisFragment();
}
if (selectedFragment != null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, selectedFragment)
.commit();
}
return true;
});
// Load default fragment
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, new EditorFragment())
.commit();
}
}
private void initializeServices() {
dictionaryService = new DictionaryService(this);
rhymeService = new RhymeService(this);
meterService = new MeterService(this);
phoneticsService = new PhoneticsService(this);
thesaurusService = new ThesaurusService(this);
}
public DictionaryService getDictionaryService() { return dictionaryService; }
public RhymeService getRhymeService() { return rhymeService; }
public MeterService getMeterService() { return meterService; }
public PhoneticsService getPhoneticsService() { return phoneticsService; }
public ThesaurusService getThesaurusService() { return thesaurusService; }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_save) {
// Handle save
return true;
} else if (item.getItemId() == R.id.action_settings) {
// Handle settings
return true;
}
return super.onOptionsItemSelected(item);
}
}
5. Models¶
Poem.java¶
package com.versepad.models;
import java.util.Date;
public class Poem {
private long id;
private String title;
private String content;
private Date createdDate;
private Date modifiedDate;
public Poem() {
this.createdDate = new Date();
this.modifiedDate = new Date();
}
public Poem(String title, String content) {
this();
this.title = title;
this.content = content;
}
// Getters and Setters
public long getId() { return id; }
public void setId(long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) {
this.title = title;
this.modifiedDate = new Date();
}
public String getContent() { return content; }
public void setContent(String content) {
this.content = content;
this.modifiedDate = new Date();
}
public Date getCreatedDate() { return createdDate; }
public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; }
public Date getModifiedDate() { return modifiedDate; }
public void setModifiedDate(Date modifiedDate) { this.modifiedDate = modifiedDate; }
}
WordAnalysis.java¶
package com.versepad.models;
import java.util.List;
public class WordAnalysis {
private String word;
private String phonetic;
private int syllableCount;
private List<String> rhymes;
private List<String> synonyms;
private String definition;
private String partOfSpeech;
private String stressPattern;
public WordAnalysis(String word) {
this.word = word;
}
// Getters and Setters
public String getWord() { return word; }
public void setWord(String word) { this.word = word; }
public String getPhonetic() { return phonetic; }
public void setPhonetic(String phonetic) { this.phonetic = phonetic; }
public int getSyllableCount() { return syllableCount; }
public void setSyllableCount(int syllableCount) { this.syllableCount = syllableCount; }
public List<String> getRhymes() { return rhymes; }
public void setRhymes(List<String> rhymes) { this.rhymes = rhymes; }
public List<String> getSynonyms() { return synonyms; }
public void setSynonyms(List<String> synonyms) { this.synonyms = synonyms; }
public String getDefinition() { return definition; }
public void setDefinition(String definition) { this.definition = definition; }
public String getPartOfSpeech() { return partOfSpeech; }
public void setPartOfSpeech(String partOfSpeech) { this.partOfSpeech = partOfSpeech; }
public String getStressPattern() { return stressPattern; }
public void setStressPattern(String stressPattern) { this.stressPattern = stressPattern; }
}
RhymeData.java¶
package com.versepad.models;
public class RhymeData {
private String word;
private String phoneticEnding;
private int syllableCount;
public RhymeData(String word, String phoneticEnding, int syllableCount) {
this.word = word;
this.phoneticEnding = phoneticEnding;
this.syllableCount = syllableCount;
}
// Getters and Setters
public String getWord() { return word; }
public void setWord(String word) { this.word = word; }
public String getPhoneticEnding() { return phoneticEnding; }
public void setPhoneticEnding(String phoneticEnding) { this.phoneticEnding = phoneticEnding; }
public int getSyllableCount() { return syllableCount; }
public void setSyllableCount(int syllableCount) { this.syllableCount = syllableCount; }
}
6. Services¶
DictionaryService.java¶
package com.versepad.services;
import android.content.Context;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
public class DictionaryService {
private Context context;
private Map<String, String> dictionary;
private Map<String, String> partOfSpeechMap;
public DictionaryService(Context context) {
this.context = context;
this.dictionary = new HashMap<>();
this.partOfSpeechMap = new HashMap<>();
loadDictionary();
}
private void loadDictionary() {
try {
InputStream is = context.getAssets().open("dictionary.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
if (line.trim().isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split("\\|");
if (parts.length >= 3) {
String word = parts[0].toLowerCase().trim();
String pos = parts[1].trim();
String definition = parts[2].trim();
dictionary.put(word, definition);
partOfSpeechMap.put(word, pos);
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public String getDefinition(String word) {
return dictionary.get(word.toLowerCase());
}
public String getPartOfSpeech(String word) {
return partOfSpeechMap.get(word.toLowerCase());
}
public boolean isValidWord(String word) {
return dictionary.containsKey(word.toLowerCase());
}
public List<String> findSimilarWords(String prefix) {
List<String> similar = new ArrayList<>();
String lowerPrefix = prefix.toLowerCase();
for (String word : dictionary.keySet()) {
if (word.startsWith(lowerPrefix) && similar.size() < 20) {
similar.add(word);
}
}
return similar;
}
}
RhymeService.java¶
package com.versepad.services;
import android.content.Context;
import com.versepad.models.RhymeData;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
public class RhymeService {
private Context context;
private Map<String, String> wordToPhonetic;
private Map<String, List<String>> phoneticToWords;
public RhymeService(Context context) {
this.context = context;
this.wordToPhonetic = new HashMap<>();
this.phoneticToWords = new HashMap<>();
loadCMUDict();
}
private void loadCMUDict() {
try {
InputStream is = context.getAssets().open("cmudict.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith(";;;")) continue;
String[] parts = line.split("\\s+", 2);
if (parts.length == 2) {
String word = parts[0].toLowerCase().replaceAll("\\(\\d+\\)", "");
String phonetic = parts[1];
wordToPhonetic.put(word, phonetic);
// Extract rhyme ending (last 2-3 phonemes)
String[] phonemes = phonetic.split("\\s+");
if (phonemes.length >= 2) {
String ending = String.join(" ",
Arrays.copyOfRange(phonemes, Math.max(0, phonemes.length - 3), phonemes.length));
phoneticToWords.computeIfAbsent(ending, k -> new ArrayList<>()).add(word);
}
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public List<String> findRhymes(String word) {
List<String> rhymes = new ArrayList<>();
String phonetic = wordToPhonetic.get(word.toLowerCase());
if (phonetic != null) {
String[] phonemes = phonetic.split("\\s+");
if (phonemes.length >= 2) {
String ending = String.join(" ",
Arrays.copyOfRange(phonemes, Math.max(0, phonemes.length - 3), phonemes.length));
List<String> candidates = phoneticToWords.get(ending);
if (candidates != null) {
for (String candidate : candidates) {
if (!candidate.equals(word.toLowerCase()) && rhymes.size() < 50) {
rhymes.add(candidate);
}
}
}
}
}
return rhymes;
}
public String getPhonetic(String word) {
return wordToPhonetic.get(word.toLowerCase());
}
public List<String> findPerfectRhymes(String word) {
// Perfect rhymes have identical sounds from the last stressed vowel
return findRhymes(word);
}
public List<String> findNearRhymes(String word) {
List<String> nearRhymes = new ArrayList<>();
String phonetic = wordToPhonetic.get(word.toLowerCase());
if (phonetic != null) {
String[] phonemes = phonetic.split("\\s+");
if (phonemes.length >= 1) {
String lastPhoneme = phonemes[phonemes.length - 1];
// Find words ending with similar sounds
for (Map.Entry<String, String> entry : wordToPhonetic.entrySet()) {
if (!entry.getKey().equals(word.toLowerCase())) {
String[] otherPhonemes = entry.getValue().split("\\s+");
if (otherPhonemes.length > 0) {
String otherLast = otherPhonemes[otherPhonemes.length - 1];
if (areSimilarPhonemes(lastPhoneme, otherLast) && nearRhymes.size() < 30) {
nearRhymes.add(entry.getKey());
}
}
}
}
}
}
return nearRhymes;
}
private boolean areSimilarPhonemes(String phone1, String phone2) {
// Remove stress markers
phone1 = phone1.replaceAll("[0-2]", "");
phone2 = phone2.replaceAll("[0-2]", "");
// Check for similar vowel sounds
String[] vowels = {"A", "E", "I", "O", "U", "AE", "AH", "AO", "AW", "AY", "EH", "ER", "EY", "IH", "IY", "OW", "OY", "UH", "UW"};
for (String vowel : vowels) {
if (phone1.startsWith(vowel) && phone2.startsWith(vowel)) {
return true;
}
}
return phone1.equals(phone2);
}
}
MeterService.java¶
package com.versepad.services;
import android.content.Context;
import java.util.*;
import java.util.regex.Pattern;
public class MeterService {
private Context context;
private RhymeService rhymeService;
private Pattern wordPattern;
public MeterService(Context context) {
this.context = context;
this.rhymeService = new RhymeService(context);
this.wordPattern = Pattern.compile("\\b\\w+\\b");
}
public String analyzeMeter(String line) {
String[] words = line.toLowerCase().replaceAll("[^a-zA-Z\\s]", "").split("\\s+");
StringBuilder meterPattern = new StringBuilder();
for (String word : words) {
if (word.trim().isEmpty()) continue;
String stressPattern = getStressPattern(word);
meterPattern.append(stressPattern).append(" ");
}
return meterPattern.toString().trim();
}
public String getStressPattern(String word) {
String phonetic = rhymeService.getPhonetic(word);
if (phonetic == null) {
return estimateStressPattern(word);
}
StringBuilder pattern = new StringBuilder();
String[] phonemes = phonetic.split("\\s+");
for (String phoneme : phonemes) {
if (phoneme.matches(".*[0-2]")) {
char stress = phoneme.charAt(phoneme.length() - 1);
switch (stress) {
case '1': pattern.append("/"); break; // Primary stress
case '2': pattern.append("\\"); break; // Secondary stress
case '0': pattern.append("u"); break; // Unstressed
}
}
}
return pattern.length() > 0 ? pattern.toString() : estimateStressPattern(word);
}
private String estimateStressPattern(String word) {
// Simple heuristic for unknown words
int syllables = countSyllables(word);
if (syllables <= 1) return "/";
if (syllables == 2) return "/u";
if (syllables == 3) return "/uu";
return "/" + "u".repeat(syllables - 1);
}
public int countSyllables(String word) {
if (word == null || word.isEmpty()) return 0;
word = word.toLowerCase().replaceAll("[^a-z]", "");
if (word.isEmpty()) return 0;
// Count vowel groups
int count = 0;
boolean previousWasVowel = false;
for (char c : word.toCharArray()) {
boolean isVowel = "aeiouy".indexOf(c) != -1;
if (isVowel && !previousWasVowel) {
count++;
}
previousWasVowel = isVowel;
}
// Adjust for silent e
if (word.endsWith("e") && count > 1) {
count--;
}
// Ensure at least 1 syllable
return Math.max(1, count);
}
public String identifyMeterType(String poem) {
String[] lines = poem.split("\n");
Map<String, Integer> meterCounts = new HashMap<>();
for (String line : lines) {
if (line.trim().isEmpty()) continue;
String meter = analyzeMeter(line);
String simplified = simplifyMeterPattern(meter);
meterCounts.put(simplified, meterCounts.getOrDefault(simplified, 0) + 1);
}
// Find most common pattern
String commonPattern = meterCounts.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("unknown");
return identifyMeterName(commonPattern);
}
private String simplifyMeterPattern(String pattern) {
return pattern.replaceAll("\\s+", "").replaceAll("\\\\", "u");
}
private String identifyMeterName(String pattern) {
// Common meter patterns
if (pattern.matches("(/u)+")) return "Iambic";
if (pattern.matches("(u/)+")) return "Trochaic";
if (pattern.matches("(uu/)+")) return "Anapestic";
if (pattern.matches("(/uu)+")) return "Dactylic";
return "Free Verse";
}
public String getVisualMeter(String line) {
String meter = analyzeMeter(line);
return meter.replace("/", "◉").replace("u", "○").replace("\\", "◐");
}
}
PhoneticsService.java¶
package com.versepad.services;
import android.content.Context;
import java.util.*;
public class PhoneticsService {
private Context context;
private RhymeService rhymeService;
private Map<String, String> phoneticToIPA;
public PhoneticsService(Context context) {
this.context = context;
this.rhymeService = new RhymeService(context);
initializePhoneticMap();
}
private void initializePhoneticMap() {
phoneticToIPA = new HashMap<>();
// Vowels
phoneticToIPA.put("AA", "ɑ");
phoneticToIPA.put("AE", "æ");
phoneticToIPA.put("AH", "ʌ");
phoneticToIPA.put("AO", "ɔ");
phoneticToIPA.put("AW", "aʊ");
phoneticToIPA.put("AY", "aɪ");
phoneticToIPA.put("EH", "ɛ");
phoneticToIPA.put("ER", "ɚ");
phoneticToIPA.put("EY", "eɪ");
phoneticToIPA.put("IH", "ɪ");
phoneticToIPA.put("IY", "i");
phoneticToIPA.put("OW", "oʊ");
phoneticToIPA.put("OY", "ɔɪ");
phoneticToIPA.put("UH", "ʊ");
phoneticToIPA.put("UW", "u");
// Consonants
phoneticToIPA.put("B", "b");
phoneticToIPA.put("CH", "tʃ");
phoneticToIPA.put("D", "d");
phoneticToIPA.put("DH", "ð");
phoneticToIPA.put("F", "f");
phoneticToIPA.put("G", "g");
phoneticToIPA.put("HH", "h");
phoneticToIPA.put("JH", "dʒ");
phoneticToIPA.put("K", "k");
phoneticToIPA.put("L", "l");
phoneticToIPA.put("M", "m");
phoneticToIPA.put("N", "n");
phoneticToIPA.put("NG", "ŋ");
phoneticToIPA.put("P", "p");
phoneticToIPA.put("R", "r");
phoneticToIPA.put("S", "s");
phoneticToIPA.put("SH", "ʃ");
phoneticToIPA.put("T", "t");
phoneticToIPA.put("TH", "θ");
phoneticToIPA.put("V", "v");
phoneticToIPA.put("W", "w");
phoneticToIPA.put("Y", "j");
phoneticToIPA.put("Z", "z");
phoneticToIPA.put("ZH", "ʒ");
}
public String getIPATranscription(String word) {
String cmuPhonetic = rhymeService.getPhonetic(word);
if (cmuPhonetic == null) {
return null;
}
StringBuilder ipa = new StringBuilder();
String[] phonemes = cmuPhonetic.split("\\s+");
for (String phoneme : phonemes) {
String cleanPhoneme = phoneme.replaceAll("[0-2]", "");
String ipaSymbol = phoneticToIPA.get(cleanPhoneme);
if (ipaSymbol != null) {
ipa.append(ipaSymbol);
// Add stress markers
if (phoneme.endsWith("1")) {
ipa.insert(ipa.length() - ipaSymbol.length(), "ˈ");
} else if (phoneme.endsWith("2")) {
ipa.insert(ipa.length() - ipaSymbol.length(), "ˌ");
}
}
}
return "/" + ipa.toString() + "/";
}
public String getCMUPhonetic(String word) {
return rhymeService.getPhonetic(word);
}
public List<String> getAllophones(String phoneme) {
List<String> allophones = new ArrayList<>();
// Common allophone patterns
switch (phoneme.replaceAll("[0-2]", "")) {
case "T":
allophones.addAll(Arrays.asList("t", "ʔ", "ɾ"));
break;
case "D":
allophones.addAll(Arrays.asList("d", "ɾ"));
break;
case "L":
allophones.addAll(Arrays.asList("l", "ɫ"));
break;
case "R":
allophones.addAll(Arrays.asList("r", "ɹ", "ɾ"));
break;
default:
String ipa = phoneticToIPA.get(phoneme);
if (ipa != null) {
allophones.add(ipa);
}
}
return allophones;
}
public boolean arePhonemesSimilar(String phoneme1, String phoneme2) {
phoneme1 = phoneme1.replaceAll("[0-2]", "");
phoneme2 = phoneme2.replaceAll("[0-2]", "");
// Check if they're the same
if (phoneme1.equals(phoneme2)) return true;
// Check if they share allophones
List<String> allo1 = getAllophones(phoneme1);
List<String> allo2 = getAllophones(phoneme2);
for (String a1 : allo1) {
if (allo2.contains(a1)) return true;
}
return false;
}
}
ThesaurusService.java¶
package com.versepad.services;
import android.content.Context;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
public class ThesaurusService {
private Context context;
private Map<String, List<String>> synonyms;
private Map<String, List<String>> antonyms;
public ThesaurusService(Context context) {
this.context = context;
this.synonyms = new HashMap<>();
this.antonyms = new HashMap<>();
loadThesaurus();
}
private void loadThesaurus() {
try {
InputStream is = context.getAssets().open("thesaurus.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = reader.readLine()) != null) {
if (line.trim().isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split("\\|");
if (parts.length >= 3) {
String word = parts[0].toLowerCase().trim();
String type = parts[1].trim(); // "syn" or "ant"
String[] words = parts[2].split(",");
List<String> wordList = new ArrayList<>();
for (String w : words) {
wordList.add(w.trim());
}
if ("syn".equals(type)) {
synonyms.put(word, wordList);
} else if ("ant".equals(type)) {
antonyms.put(word, wordList);
}
}
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public List<String> getSynonyms(String word) {
List<String> result = synonyms.get(word.toLowerCase());
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
public List<String> getAntonyms(String word) {
List<String> result = antonyms.get(word.toLowerCase());
return result != null ? new ArrayList<>(result) : new ArrayList<>();
}
public List<String> getRelatedWords(String word) {
List<String> related = new ArrayList<>();
related.addAll(getSynonyms(word));
related.addAll(getAntonyms(word));
return related;
}
public List<String> findWordsByMeaning(String concept) {
List<String> matches = new ArrayList<>();
String lowerConcept = concept.toLowerCase();
for (Map.Entry<String, List<String>> entry : synonyms.entrySet()) {
if (entry.getValue().stream().anyMatch(syn -> syn.toLowerCase().contains(lowerConcept))) {
matches.add(entry.getKey());
}
}
return matches;
}
}
7. Fragments¶
EditorFragment.java¶
package com.versepad.fragments;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.versepad.MainActivity;
import com.versepad.R;
import com.versepad.adapters.SuggestionAdapter;
import com.versepad.models.WordAnalysis;
import java.util.ArrayList;
import java.util.List;
public class EditorFragment extends Fragment {
private EditText editTitle;
private EditText editContent;
private TextView textWordCount;
private TextView textLineCount;
private RecyclerView recyclerSuggestions;
private SuggestionAdapter suggestionAdapter;
private MainActivity mainActivity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_editor, container, false);
mainActivity = (MainActivity) getActivity();
initializeViews(view);
setupTextWatchers();
setupSuggestions();
return view;
}
private void initializeViews(View view) {
editTitle = view.findViewById(R.id.edit_title);
editContent = view.findViewById(R.id.edit_content);
textWordCount = view.findViewById(R.id.text_word_count);
textLineCount = view.findViewById(R.id.text_line_count);
recyclerSuggestions = view.findViewById(R.id.recycler_suggestions);
}
private void setupTextWatchers() {
editContent.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
updateStats();
updateSuggestions();
}
@Override
public void afterTextChanged(Editable s) {}
});
}
private void setupSuggestions() {
suggestionAdapter = new SuggestionAdapter(new ArrayList<>(), word -> {
// Insert word at cursor position
int cursorPos = editContent.getSelectionStart();
String currentText = editContent.getText().toString();
String newText = currentText.substring(0, cursorPos) + word + currentText.substring(cursorPos);
editContent.setText(newText);
editContent.setSelection(cursorPos + word.length());
});
recyclerSuggestions.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false));
recyclerSuggestions.setAdapter(suggestionAdapter);
}
private void updateStats() {
String content = editContent.getText().toString();
// Word count
String[] words = content.trim().split("\\s+");
int wordCount = content.trim().isEmpty() ? 0 : words.length;
// Line count
int lineCount = content.split("\n").length;
textWordCount.setText("Words: " + wordCount);
textLineCount.setText("Lines: " + lineCount);
}
private void updateSuggestions() {
String content = editContent.getText().toString();
int cursorPos = editContent.getSelectionStart();
// Get current word
String currentWord = getCurrentWord(content, cursorPos);
if (currentWord.length() < 2) {
suggestionAdapter.updateSuggestions(new ArrayList<>());
return;
}
// Get suggestions based on context
List<String> suggestions = new ArrayList<>();
// Rhyme suggestions
List<String> rhymes = mainActivity.getRhymeService().findRhymes(currentWord);
suggestions.addAll(rhymes.subList(0, Math.min(5, rhymes.size())));
// Synonym suggestions
List<String> synonyms = mainActivity.getThesaurusService().getSynonyms(currentWord);
suggestions.addAll(synonyms.subList(0, Math.min(5, synonyms.size())));
// Similar words
List<String> similar = mainActivity.getDictionaryService().findSimilarWords(currentWord);
suggestions.addAll(similar.subList(0, Math.min(5, similar.size())));
suggestionAdapter.updateSuggestions(suggestions);
}
private String getCurrentWord(String text, int cursorPos) {
if (cursorPos <= 0 || cursorPos > text.length()) return "";
int start = cursorPos - 1;
int end = cursorPos;
// Find word boundaries
while (start >= 0 && Character.isLetterOrDigit(text.charAt(start))) {
start--;
}
start++;
while (end < text.length() && Character.isLetterOrDigit(text.charAt(end))) {
end++;
}
return text.substring(start, end);
}
public String getTitle() {
return editTitle.getText().toString();
}
public String getContent() {
return editContent.getText().toString();
}
public void setTitle(String title) {
editTitle.setText(title);
}
public void setContent(String content) {
editContent.setText(content);
updateStats();
}
}
AnalysisFragment.java¶
package com.versepad.fragments;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
import com.versepad.MainActivity;
import com.versepad.R;
public class AnalysisFragment extends Fragment {
private TextView textMeterAnalysis;
private TextView textRhymeScheme;
private TextView textPhoneticAnalysis;
private TextView textStructureAnalysis;
private MainActivity mainActivity;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_analysis, container, false);
mainActivity = (MainActivity) getActivity();
initializeViews(view);
performAnalysis();
return view;
}
private void initializeViews(View view) {
textMeterAnalysis = view.findViewById(R.id.text_meter_analysis);
textRhymeScheme = view.findViewById(R.id.text_rhyme_scheme);
textPhoneticAnalysis = view.findViewById(R.id.text_phonetic_analysis);
textStructureAnalysis = view.findViewById(R.id.text_structure_analysis);
}
private void performAnalysis() {
// Get poem content from editor fragment
String poemContent = getSamplePoem(); // You would get this from the editor
if (poemContent.trim().isEmpty()) {
showEmptyState();
return;
}
analyzeMeter(poemContent);
analyzeRhymeScheme(poemContent);
analyzePhonetics(poemContent);
analyzeStructure(poemContent);
}
private void analyzeMeter(String poem) {
StringBuilder analysis = new StringBuilder();
String[] lines = poem.split("\n");
analysis.append("METER ANALYSIS:\n\n");
for (int i = 0; i < lines.length; i++) {
if (lines[i].trim().isEmpty()) continue;
String meter = mainActivity.getMeterService().analyzeMeter(lines[i]);
String visual = mainActivity.getMeterService().getVisualMeter(lines[i]);
analysis.append("Line ").append(i + 1).append(":\n");
analysis.append(lines[i]).append("\n");
analysis.append(visual).append("\n");
analysis.append("Pattern: ").append(meter).append("\n\n");
}
String meterType = mainActivity.getMeterService().identifyMeterType(poem);
analysis.append("Overall Meter: ").append(meterType);
textMeterAnalysis.setText(analysis.toString());
}
private void analyzeRhymeScheme(String poem) {
StringBuilder analysis = new StringBuilder();
String[] lines = poem.split("\n");
analysis.append("RHYME ANALYSIS:\n\n");
char currentScheme = 'A';
for (int i = 0; i < lines.length; i++) {
if (lines[i].trim().isEmpty()) continue;
String lastWord = getLastWord(lines[i]);
if (!lastWord.isEmpty()) {
analysis.append("Line ").append(i + 1).append(": ");
analysis.append(lastWord).append(" (").append(currentScheme).append(")\n");
// Get rhymes for the last word
java.util.List<String> rhymes = mainActivity.getRhymeService().findRhymes(lastWord);
if (!rhymes.isEmpty()) {
analysis.append("Rhymes: ");
for (int j = 0; j < Math.min(5, rhymes.size()); j++) {
analysis.append(rhymes.get(j));
if (j < Math.min(4, rhymes.size() - 1)) analysis.append(", ");
}
analysis.append("\n");
}
analysis.append("\n");
}
currentScheme++;
}
textRhymeScheme.setText(analysis.toString());
}
private void analyzePhonetics(String poem) {
StringBuilder analysis = new StringBuilder();
String[] words = poem.toLowerCase().replaceAll("[^a-zA-Z\\s]", "").split("\\s+");
analysis.append("PHONETIC ANALYSIS:\n\n");
java.util.Set<String> uniqueWords = new java.util.LinkedHashSet<>();
for (String word : words) {
if (word.length() > 2) {
uniqueWords.add(word);
}
}
int count = 0;
for (String word : uniqueWords) {
if (count >= 10) break; // Limit to first 10 unique words
String ipa = mainActivity.getPhoneticsService().getIPATranscription(word);
String cmu = mainActivity.getPhoneticsService().getCMUPhonetic(word);
analysis.append(word).append(":\n");
if (ipa != null) {
analysis.append(" IPA: ").append(ipa).append("\n");
}
if (cmu != null) {
analysis.append(" CMU: ").append(cmu).append("\n");
}
analysis.append("\n");
count++;
}
textPhoneticAnalysis.setText(analysis.toString());
}
private void analyzeStructure(String poem) {
StringBuilder analysis = new StringBuilder();
String[] lines = poem.split("\n");
analysis.append("STRUCTURE ANALYSIS:\n\n");
int totalLines = 0;
int totalWords = 0;
int totalSyllables = 0;
for (String line : lines) {
if (!line.trim().isEmpty()) {
totalLines++;
String[] words = line.trim().split("\\s+");
totalWords += words.length;
for (String word : words) {
totalSyllables += mainActivity.getMeterService().countSyllables(word);
}
}
}
analysis.append("Total Lines: ").append(totalLines).append("\n");
analysis.append("Total Words: ").append(totalWords).append("\n");
analysis.append("Total Syllables: ").append(totalSyllables).append("\n");
if (totalLines > 0) {
analysis.append("Average Words per Line: ").append(String.format("%.1f", (double) totalWords / totalLines)).append("\n");
analysis.append("Average Syllables per Line: ").append(String.format("%.1f", (double) totalSyllables / totalLines)).append("\n");
}
textStructureAnalysis.setText(analysis.toString());
}
private String getLastWord(String line) {
String[] words = line.trim().replaceAll("[^a-zA-Z\\s]", "").split("\\s+");
return words.length > 0 ? words[words.length - 1].toLowerCase() : "";
}
private String getSamplePoem() {
// This would normally get content from the editor fragment
return "Roses are red\nViolets are blue\nSugar is sweet\nAnd so are you";
}
private void showEmptyState() {
textMeterAnalysis.setText("No poem to analyze. Please write something in the editor first.");
textRhymeScheme.setText("");
textPhoneticAnalysis.setText("");
textStructureAnalysis.setText("");
}
}
8. Adapters¶
SuggestionAdapter.java¶
package com.versepad.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.versepad.R;
import java.util.List;
public class SuggestionAdapter extends RecyclerView.Adapter<SuggestionAdapter.SuggestionViewHolder> {
private List<String> suggestions;
private OnSuggestionClickListener listener;
public interface OnSuggestionClickListener {
void onSuggestionClick(String suggestion);
}
public SuggestionAdapter(List<String> suggestions, OnSuggestionClickListener listener) {
this.suggestions = suggestions;
this.listener = listener;
}
@NonNull
@Override
public SuggestionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_suggestion, parent, false);
return new SuggestionViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SuggestionViewHolder holder, int position) {
String suggestion = suggestions.get(position);
holder.textSuggestion.setText(suggestion);
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onSuggestionClick(suggestion);
}
});
}
@Override
public int getItemCount() {
return suggestions.size();
}
public void updateSuggestions(List<String> newSuggestions) {
this.suggestions = newSuggestions;
notifyDataSetChanged();
}
static class SuggestionViewHolder extends RecyclerView.ViewHolder {
TextView textSuggestion;
SuggestionViewHolder(@NonNull View itemView) {
super(itemView);
textSuggestion = itemView.findViewById(R.id.text_suggestion);
}
}
}
PoemListAdapter.java¶
package com.versepad.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.versepad.R;
import com.versepad.models.Poem;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
public class PoemListAdapter extends RecyclerView.Adapter<PoemListAdapter.PoemViewHolder> {
private List<Poem> poems;
private OnPoemClickListener listener;
private SimpleDateFormat dateFormat;
public interface OnPoemClickListener {
void onPoemClick(Poem poem);
void onPoemLongClick(Poem poem);
}
public PoemListAdapter(List<Poem> poems, OnPoemClickListener listener) {
this.poems = poems;
this.listener = listener;
this.dateFormat = new SimpleDateFormat("MMM dd, yyyy", Locale.getDefault());
}
@NonNull
@Override
public PoemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_poem, parent, false);
return new PoemViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull PoemViewHolder holder, int position) {
Poem poem = poems.get(position);
holder.textTitle.setText(poem.getTitle().isEmpty() ? "Untitled" : poem.getTitle());
holder.textPreview.setText(getPreview(poem.getContent()));
holder.textDate.setText(dateFormat.format(poem.getModifiedDate()));
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onPoemClick(poem);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (listener != null) {
listener.onPoemLongClick(poem);
}
return true;
});
}
@Override
public int getItemCount() {
return poems.size();
}
private String getPreview(String content) {
if (content.length() <= 100) {
return content;
}
return content.substring(0, 100) + "...";
}
public void updatePoems(List<Poem> newPoems) {
this.poems = newPoems;
notifyDataSetChanged();
}
static class PoemViewHolder extends RecyclerView.ViewHolder {
TextView textTitle;
TextView textPreview;
TextView textDate;
PoemViewHolder(@NonNull View itemView) {
super(itemView);
textTitle = itemView.findViewById(R.id.text_title);
textPreview = itemView.findViewById(R.id.text_preview);
textDate = itemView.findViewById(R.id.text_date);
}
}
}
9. Utils¶
DatabaseHelper.java¶
package com.versepad.utils;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.versepad.models.Poem;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "versepad.db";
private static final int DATABASE_VERSION = 1;
private static final String TABLE_POEMS = "poems";
private static final String COLUMN_ID = "id";
private static final String COLUMN_TITLE = "title";
private static final String COLUMN_CONTENT = "content";
private static final String COLUMN_CREATED_DATE = "created_date";
private static final String COLUMN_MODIFIED_DATE = "modified_date";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String createTable = "CREATE TABLE " + TABLE_POEMS + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_TITLE + " TEXT, " +
COLUMN_CONTENT + " TEXT, " +
COLUMN_CREATED_DATE + " INTEGER, " +
COLUMN_MODIFIED_DATE + " INTEGER)";
db.execSQL(createTable);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_POEMS);
onCreate(db);
}
public long insertPoem(Poem poem) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_TITLE, poem.getTitle());
values.put(COLUMN_CONTENT, poem.getContent());
values.put(COLUMN_CREATED_DATE, poem.getCreatedDate().getTime());
values.put(COLUMN_MODIFIED_DATE, poem.getModifiedDate().getTime());
long id = db.insert(TABLE_POEMS, null, values);
db.close();
return id;
}
public void updatePoem(Poem poem) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_TITLE, poem.getTitle());
values.put(COLUMN_CONTENT, poem.getContent());
values.put(COLUMN_MODIFIED_DATE, new Date().getTime());
db.update(TABLE_POEMS, values, COLUMN_ID + " = ?", new String[]{String.valueOf(poem.getId())});
db.close();
}
public void deletePoem(long id) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_POEMS, COLUMN_ID + " = ?", new String[]{String.valueOf(id)});
db.close();
}
public List<Poem> getAllPoems() {
List<Poem> poems = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_POEMS, null, null, null, null, null, COLUMN_MODIFIED_DATE + " DESC");
if (cursor.moveToFirst()) {
do {
Poem poem = new Poem();
poem.setId(cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
poem.setTitle(cursor.getString(cursor.getColumnIndex(COLUMN_TITLE)));
poem.setContent(cursor.getString(cursor.getColumnIndex(COLUMN_CONTENT)));
poem.setCreatedDate(new Date(cursor.getLong(cursor.getColumnIndex(COLUMN_CREATED_DATE))));
poem.setModifiedDate(new Date(cursor.getLong(cursor.getColumnIndex(COLUMN_MODIFIED_DATE))));
poems.add(poem);
} while (cursor.moveToNext());
}
cursor.close();
db.close();
return poems;
}
public Poem getPoemById(long id) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_POEMS, null, COLUMN_ID + " = ?", new String[]{String.valueOf(id)}, null, null, null);
Poem poem = null;
if (cursor.moveToFirst()) {
poem = new Poem();
poem.setId(cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
poem.setTitle(cursor.getString(cursor.getColumnIndex(COLUMN_TITLE)));
poem.setContent(cursor.getString(cursor.getColumnIndex(COLUMN_CONTENT)));
poem.setCreatedDate(new Date(cursor.getLong(cursor.getColumnIndex(COLUMN_CREATED_DATE))));
poem.setModifiedDate(new Date(cursor.getLong(cursor.getColumnIndex(COLUMN_MODIFIED_DATE))));
}
cursor.close();
db.close();
return poem;
}
}
FileUtils.java package com.versepad.utils;
import android.content.Context; import android.os.Environment; import android.widget.Toast; import com.versepad.models.Poem; import java.io.*; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale;
public class FileUtils { private static final String POEMS_DIRECTORY = "VersePad"; private static final String FILE_EXTENSION = ".txt";
public static boolean saveToFile(Context context, Poem poem) {
try {
File documentsDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), POEMS_DIRECTORY);
if (!documentsDir.exists()) {
documentsDir.mkdirs();
}
String fileName = sanitizeFileName(poem.getTitle().isEmpty() ? "Untitled" : poem.getTitle()) + FILE_EXTENSION;
File file = new File(documentsDir, fileName);
FileWriter writer = new FileWriter(file);
writer.write("Title: " + poem.getTitle() + "\n");
writer.write("Created: " + formatDate(poem.getCreatedDate()) + "\n");
writer.write("Modified: " + formatDate(poem.getModifiedDate()) + "\n");
writer.write("\n" + poem.getContent());
writer.close();
Toast.makeText(context, "Poem saved to " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
return true;
} catch (IOException e) {
Toast.makeText(context, "Error saving file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
return false;
}
}
public static String loadFromFile(Context context, String fileName) {
try {
File documentsDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), POEMS_DIRECTORY);
File file = new File(documentsDir, fileName);
if (!file.exists()) {
return null;
}
BufferedReader reader = new BufferedReader(new FileReader(file));
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
reader.close();
return content.toString();
} catch (IOException e) {
Toast.makeText(context, "Error loading file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
return null;
}
}
public static boolean exportToText(Context context, Poem poem) {
return saveToFile(context, poem);
}
public static String sharePoem(Poem poem) {
StringBuilder shareText = new StringBuilder();
shareText.append("Title: ").append(poem.getTitle()).append("\n\n");
shareText.append(poem.getContent()).append("\n\n");
shareText.append("Created with VersePad");
return shareText.toString();
}
private static String sanitizeFileName(String fileName) {
return fileName.replaceAll("[^a-zA-Z0-9._-]", "_");
}
private static String formatDate(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("MMM dd, yyyy HH:mm", Locale.getDefault());
return formatter.format(date);
}
public static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
public static boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
}
}
activity_main.xml
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:menu="@menu/bottom_navigation" />
fragment_editor.xml
<EditText
android:id="@+id/edit_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/hint_title"
android:textSize="18sp"
android:textStyle="bold"
android:background="@android:color/transparent"
android:padding="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/edit_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<EditText
android:id="@+id/edit_content"
android:layout_width="0dp"
android:layout_height="0dp"
android:hint="@string/hint_content"
android:gravity="top"
android:textSize="16sp"
android:background="@android:color/transparent"
android:padding="8dp"
android:scrollbars="vertical"
android:inputType="textMultiLine"
app:layout_constraintTop_toBottomOf="@+id/divider"
app:layout_constraintBottom_toTopOf="@+id/stats_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<LinearLayout
android:id="@+id/stats_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp"
android:background="@color/light_gray"
app:layout_constraintBottom_toTopOf="@+id/recycler_suggestions"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:id="@+id/text_word_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/words_0"
android:textSize="12sp"
android:textColor="@color/dark_gray" />
<TextView
android:id="@+id/text_line_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/lines_0"
android:textSize="12sp"
android:textColor="@color/dark_gray"
android:gravity="end" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_suggestions"
android:layout_width="0dp"
android:layout_height="60dp"
android:background="@color/suggestion_background"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
fragment_analysis.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/meter_analysis"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/primary_text"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/text_meter_analysis"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_content_to_analyze"
android:textSize="14sp"
android:fontFamily="monospace"
android:textColor="@color/secondary_text" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/rhyme_analysis"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/primary_text"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/text_rhyme_scheme"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_content_to_analyze"
android:textSize="14sp"
android:fontFamily="monospace"
android:textColor="@color/secondary_text" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/phonetic_analysis"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/primary_text"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/text_phonetic_analysis"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_content_to_analyze"
android:textSize="14sp"
android:fontFamily="monospace"
android:textColor="@color/secondary_text" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="8dp"
app:cardElevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/structure_analysis"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/primary_text"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/text_structure_analysis"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/no_content_to_analyze"
android:textSize="14sp"
android:fontFamily="monospace"
android:textColor="@color/secondary_text" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
item_suggestion.xml
<TextView
android:id="@+id/text_suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="6dp"
android:paddingBottom="6dp"
android:textSize="14sp"
android:textColor="@color/suggestion_text"
android:singleLine="true"
android:background="?android:attr/selectableItemBackground" />
item_poem.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Untitled"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/primary_text"
android:layout_marginBottom="4dp" />
<TextView
android:id="@+id/text_preview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Poem preview..."
android:textSize="14sp"
android:textColor="@color/secondary_text"
android:maxLines="3"
android:ellipsize="end"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/text_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Jan 01, 2025"
android:textSize="12sp"
android:textColor="@color/tertiary_text"
android:gravity="end" />
</LinearLayout>
strings.xml
<!-- Navigation -->
<string name="nav_editor">Editor</string>
<string name="nav_analysis">Analysis</string>
<string name="nav_library">Library</string>
<!-- Menu -->
<string name="action_save">Save</string>
<string name="action_settings">Settings</string>
<string name="action_export">Export</string>
<string name="action_share">Share</string>
<string name="action_delete">Delete</string>
<!-- Editor -->
<string name="hint_title">Enter poem title...</string>
<string name="hint_content">Write your poem here...</string>
<string name="words_0">Words: 0</string>
<string name="lines_0">Lines: 0</string>
<!-- Analysis -->
<string name="meter_analysis">Meter Analysis</string>
<string name="rhyme_analysis">Rhyme Analysis</string>
<string name="phonetic_analysis">Phonetic Analysis</string>
<string name="structure_analysis">Structure Analysis</string>
<string name="no_content_to_analyze">No content to analyze. Please write something in the editor first.</string>
<!-- Messages -->
<string name="poem_saved">Poem saved successfully</string>
<string name="poem_deleted">Poem deleted</string>
<string name="confirm_delete">Are you sure you want to delete this poem?</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="cancel">Cancel</string>
<string name="ok">OK</string>
<!-- Errors -->
<string name="error_saving">Error saving poem</string>
<string name="error_loading">Error loading poem</string>
<string name="error_permission">Storage permission required</string>
<!-- Suggestions -->
<string name="suggestions_rhymes">Rhymes</string>
<string name="suggestions_synonyms">Synonyms</string>
<string name="suggestions_similar">Similar</string>
<!-- Export -->
<string name="export_success">Poem exported successfully</string>
<string name="export_failed">Failed to export poem</string>
<string name="share_poem">Share Poem</string>
colors.xml
<!-- Background Colors -->
<color name="background">#FFFFFF</color>
<color name="surface">#FFFFFF</color>
<color name="error">#B00020</color>
<!-- Text Colors -->
<color name="primary_text">#212121</color>
<color name="secondary_text">#757575</color>
<color name="tertiary_text">#9E9E9E</color>
<color name="on_primary">#FFFFFF</color>
<color name="on_secondary">#000000</color>
<color name="on_background">#000000</color>
<color name="on_surface">#000000</color>
<color name="on_error">#FFFFFF</color>
<!-- Custom Colors -->
<color name="light_gray">#F5F5F5</color>
<color name="dark_gray">#616161</color>
<color name="accent">#FF4081</color>
<!-- Suggestion Colors -->
<color name="suggestion_background">#FAFAFA</color>
<color name="suggestion_card">#E8F5E8</color>
<color name="suggestion_text">#2E7D32</color>
<!-- Status Colors -->
<color name="success">#4CAF50</color>
<color name="warning">#FF9800</color>
<color name="info">#2196F3</color>
<!-- Rhyme Colors -->
<color name="rhyme_a">#FFCDD2</color>
<color name="rhyme_b">#C8E6C9</color>
<color name="rhyme_c">#BBDEFB</color>
<color name="rhyme_d">#F8BBD9</color>
<color name="rhyme_e">#FFECB3</color>
styles.xml
<!-- No Action Bar theme for splash screen -->
<style name="AppTheme.NoActionBar" parent="AppTheme">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<!-- Editor specific styles -->
<style name="EditorTitle">
<item name="android:textSize">20sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/primary_text</item>
<item name="android:padding">12dp</item>
</style>
<style name="EditorContent">
<item name="android:textSize">16sp</item>
<item name="android:textColor">@color/primary_text</item>
<item name="android:lineSpacingExtra">4dp</item>
<item name="android:padding">12dp</item>
</style>
<!-- Analysis styles -->
<style name="AnalysisTitle">
<item name="android:textSize">18sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@color/primary</item>
<item name="android:layout_marginBottom">8dp</item>
</style>
<style name="AnalysisContent">
<item name="android:textSize">14sp</item>
<item name="android:textColor">@color/secondary_text</item>
<item name="android:fontFamily">monospace</item>
<item name="android:lineSpacingExtra">2dp</item>
</style>
<!-- Button styles -->
<style name="PrimaryButton" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@color/primary</item>
<item name="android:textColor">@color/on_primary</item>
<item name="cornerRadius">8dp</item>
</style>
<style name="SecondaryButton" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/primary</item>
<item name="android:textColor">@color/primary</item>
<item name="cornerRadius">8dp</item>
</style>
<!-- Card styles -->
<style name="AnalysisCard">
<item name="cardCornerRadius">12dp</item>
<item name="cardElevation">4dp</item>
<item name="android:layout_margin">8dp</item>
<item name="cardBackgroundColor">@color/surface</item>
</style>
<style name="SuggestionChip">
<item name="chipBackgroundColor">@color/suggestion_card</item>
<item name="android:textColor">@color/suggestion_
To replace the textual dictionary and thesaurus files with libraries, you can use the following Java libraries that provide dictionary and thesaurus functionality:
1. Replace dictionary.txt
with a Library¶
For dictionary functionality (definitions, parts of speech, etc.), you can use: - WordNet (via JAWS or JWI): A lexical database for English. - Library: JAWS (Java API for WordNet Searching) - Example:
import net.sf.extjwnl.dictionary.Dictionary;
public class DictionaryService {
private Dictionary dictionary;
public DictionaryService(Context context) {
try {
this.dictionary = Dictionary.getDefaultResourceInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
public String getDefinition(String word) {
try {
IndexWord indexWord = dictionary.lookupIndexWord(POS.NOUN, word);
if (indexWord != null) {
Synset[] senses = indexWord.getSenses();
return senses[0].getGloss(); // Return the first definition
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2. Replace thesaurus.txt
with a Library¶
For thesaurus functionality (synonyms, antonyms), you can use: - WordNet (same as above): Provides synonyms and antonyms. - Example:
public List<String> getSynonyms(String word) {
List<String> synonyms = new ArrayList<>();
try {
IndexWord indexWord = dictionary.lookupIndexWord(POS.NOUN, word);
if (indexWord != null) {
for (Synset synset : indexWord.getSenses()) {
for (Word w : synset.getWords()) {
synonyms.add(w.getLemma());
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return synonyms;
}
3. Replace cmudict.txt
with a Library¶
For phonetic and rhyming functionality, you can use: - CMU Pronouncing Dictionary (via a Java wrapper): - Library: cmudict-java - Example:
import edu.cmu.sphinx.cmu.dict.CMUDict;
public class RhymeService {
private CMUDict cmuDict;
public RhymeService(Context context) {
this.cmuDict = new CMUDict();
}
public String getPhonetic(String word) {
return cmuDict.getPhonemes(word);
}
}
Updated build.gradle
(Module: app)¶
Add the dependencies for these libraries:
dependencies {
// WordNet (for dictionary and thesaurus)
implementation 'net.sf.extjwnl:extjwnl:2.0.2'
implementation 'net.sf.extjwnl:extjwnl-data-wn31:2.0.2'
// CMU Pronouncing Dictionary (for phonetics and rhymes)
implementation 'edu.cmu.sphinx:cmudict:1.0.0'
// Other existing dependencies...
}
Key Changes:¶
- Remove the
assets/
files (dictionary.txt
,thesaurus.txt
,cmudict.txt
) since they are no longer needed. - Update
DictionaryService
,ThesaurusService
, andRhymeService
to use the libraries instead of file parsing. - Ensure offline functionality by bundling the required data files with the app (WordNet data files can be included in
assets/
if needed).
Benefits:¶
- No manual file parsing: Libraries handle the data loading and querying.
- Better accuracy: WordNet and CMU Dict are well-maintained resources.
- Scalability: Easier to extend functionality (e.g., adding more linguistic features).
Let me know if you'd like help implementing any of these changes!