Skip to content
Draft
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
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# EBookReader

EBookReader is a feature-rich Android application for reading and interacting with EPUB and PDF files. It provides a seamless reading experience with functionalities for highlighting text, taking notes, and visualizing notes as a mind map.

## Features

* **Dual Format Support**: Open and read both EPUB and PDF files.
* **Recent Books**: Your recently opened books are saved for quick access.
* **File Picker**: Browse and open ebooks from your device's storage.
* **EPUB Highlighting**: Highlight important sections in your EPUBs.
* **Note-Taking**: Add notes to any book.
* **Mind Maps**: Generate a mind map from your notes to visualize key concepts.
* **PDF Annotation**: Add annotations directly onto your PDF documents.

## Setup

To build and run this project, you'll need Android Studio.

1. **Clone the repository:**
```bash
git clone https://github.com/your-username/ebook-reader.git
```
2. **Open in Android Studio:**
Open Android Studio, select 'Open an existing Android Studio project', and navigate to the cloned repository folder.
3. **Build the project:**
Android Studio should automatically sync the Gradle project. If not, you can manually trigger a sync by going to `File > Sync Project with Gradle Files`.
4. **Run the application:**
You can run the application on an Android emulator or a physical device. Select your desired run configuration and click the 'Run' button.

## Usage

### Opening a Book

You can open a book in two ways:

1. **From Assets**: The application comes with pre-packaged books in the `assets` folder. These will be displayed on the main screen when you first launch the app.
2. **From Device Storage**: Click the 'Browse' button to open a file picker and select an `.epub` or `.pdf` file from your device.

### Reading and Interaction

* **EPUBs**: When you open an EPUB file, you can highlight text by long-pressing and selecting the desired text.
* **PDFs**: For PDF files, you can enable 'Annotation Mode' to add text annotations by tapping on the document.

### Notes and Mind Maps

For any book, you can:

* **View/Add Notes**: Click the 'Notes' button next to a book on the main screen to view existing notes or add new ones.
* **Summarize Notes**: In the notes view, you can see a summary of all your notes.
* **Generate Mind Map**: Also in the notes view, you can generate a visual mind map from your notes.
21 changes: 21 additions & 0 deletions app/src/main/java/com/example/ebookreader/AddNoteActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*

/**
* This activity allows the user to add a new note to a specific book.
* It provides a simple interface with an EditText for the note text and a save button.
*/
class AddNoteActivity : AppCompatActivity() {

private var bookTitle: String? = null

/**
* Initializes the activity, sets up the UI, and handles the save button click.
*
* @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Otherwise it is null.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_note)
Expand All @@ -33,6 +42,12 @@ class AddNoteActivity : AppCompatActivity() {
}
}

/**
* Saves a new note to a file associated with the book.
*
* @param bookTitle The title of the book to which the note is being added.
* @param noteText The text of the note to be saved.
*/
private fun saveNote(bookTitle: String, noteText: String) {
val notes = loadNotes(bookTitle).toMutableList()
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
Expand All @@ -52,6 +67,12 @@ class AddNoteActivity : AppCompatActivity() {
}
}

/**
* Loads the list of notes for a specific book from a JSON file.
*
* @param bookTitle The title of the book for which to load the notes.
* @return A list of Note objects. Returns an empty list if the file doesn't exist or an error occurs.
*/
private fun loadNotes(bookTitle: String): List<Note> {
try {
val file = File(filesDir, "${Utils.sha256(bookTitle)}_notes.json")
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/example/ebookreader/Book.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
package com.example.ebookreader

/**
* Represents a book in the application.
*
* @property title The title of the book. This can be a file path or an asset name.
*/
data class Book(val title: String)
34 changes: 34 additions & 0 deletions app/src/main/java/com/example/ebookreader/BookAdapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,34 @@ import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

/**
* An adapter for displaying a list of books in a RecyclerView.
*
* @param books The list of books to be displayed.
* @param onItemClick A lambda function to be invoked when an item is clicked. The boolean parameter is true if the notes button is clicked, false otherwise.
*/
class BookAdapter(private val books: List<Book>, private val onItemClick: (Book, Boolean) -> Unit) :
RecyclerView.Adapter<BookAdapter.BookViewHolder>() {

/**
* Creates a new ViewHolder for a book item.
*
* @param parent The ViewGroup into which the new View will be added after it is bound to an adapter position.
* @param viewType The view type of the new View.
* @return A new BookViewHolder that holds a View of the given view type.
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.book_item, parent, false)
return BookViewHolder(view)
}

/**
* Binds the data to the ViewHolder at the specified position.
*
* @param holder The ViewHolder which should be updated to represent the contents of the item at the given position in the data set.
* @param position The position of the item within the adapter's data set.
*/
override fun onBindViewHolder(holder: BookViewHolder, position: Int) {
val book = books[position]
holder.bind(book)
Expand All @@ -25,11 +44,26 @@ class BookAdapter(private val books: List<Book>, private val onItemClick: (Book,
}
}

/**
* Returns the total number of items in the data set held by the adapter.
*
* @return The total number of items in this adapter.
*/
override fun getItemCount(): Int = books.size

/**
* A ViewHolder for a book item in the RecyclerView.
*
* @param itemView The view for the book item.
*/
class BookViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val titleTextView: TextView = itemView.findViewById(R.id.book_title)

/**
* Binds a book to the ViewHolder.
*
* @param book The book to be displayed.
*/
fun bind(book: Book) {
titleTextView.text = book.title
}
Expand Down
41 changes: 41 additions & 0 deletions app/src/main/java/com/example/ebookreader/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ import java.io.File
import java.io.FileReader
import java.io.FileWriter

/**
* The main entry point of the application.
* This activity displays a list of recent and asset-based books. It allows users to browse for new books from their device.
* It also handles the opening of EPUB and PDF files and manages highlights and recent book lists.
*/
class MainActivity : AppCompatActivity() {

private val PREFS_NAME = "EBookReaderPrefs"
Expand Down Expand Up @@ -50,6 +55,11 @@ class MainActivity : AppCompatActivity() {
}
}

/**
* Initializes the activity, sets up the RecyclerView for books, and handles UI interactions.
*
* @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Otherwise it is null.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Expand Down Expand Up @@ -95,6 +105,9 @@ class MainActivity : AppCompatActivity() {
}
}

/**
* Opens the system's file picker to allow the user to select an EPUB or PDF file.
*/
private fun openFilePicker() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
Expand All @@ -104,6 +117,11 @@ class MainActivity : AppCompatActivity() {
filePickerLauncher.launch(intent)
}

/**
* Saves a highlight to a JSON file specific to the book.
*
* @param highlight The highlight object to be saved.
*/
private fun saveHighlight(highlight: HighLight) {
val highlights = loadHighlights(highlight.bookId).toMutableList()
highlights.add(highlight)
Expand All @@ -120,6 +138,12 @@ class MainActivity : AppCompatActivity() {
}
}

/**
* Loads highlights for a specific book from its JSON file.
*
* @param bookId The unique identifier for the book.
* @return A list of HighLight objects. Returns an empty list if the file doesn't exist or an error occurs.
*/
private fun loadHighlights(bookId: String): List<HighLight> {
try {
val file = File(filesDir, "${Utils.sha256(bookId)}_highlights.json")
Expand All @@ -135,12 +159,22 @@ class MainActivity : AppCompatActivity() {
return emptyList()
}

/**
* Saves the list of recent books to SharedPreferences.
*
* @param books The list of books to be saved.
*/
private fun saveRecentBooks(books: List<Book>) {
val gson = Gson()
val json = gson.toJson(books)
prefs.edit().putString(RECENT_BOOKS_KEY, json).apply()
}

/**
* Loads the list of recent books from SharedPreferences.
*
* @return A list of recent Book objects. Returns an empty list if no recent books are found.
*/
private fun loadRecentBooks(): List<Book> {
val gson = Gson()
val json = prefs.getString(RECENT_BOOKS_KEY, null)
Expand All @@ -152,6 +186,13 @@ class MainActivity : AppCompatActivity() {
}
}

/**
* Adds a book to the top of the recent books list.
* If the book is already in the list, it is not added again.
* The list is capped at a maximum size defined by `MAX_RECENT_BOOKS`.
*
* @param book The book to be added.
*/
private fun addRecentBook(book: Book) {
if (!recentBooks.contains(book)) {
recentBooks.add(0, book)
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/example/ebookreader/MindMapActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,17 @@ import com.google.gson.reflect.TypeToken
import java.io.File
import java.io.FileReader

/**
* This activity displays a mind map generated from the notes of a specific book.
* It uses a WebView to render the mind map using the markmap-autoloader.js library.
*/
class MindMapActivity : AppCompatActivity() {

/**
* Initializes the activity, sets up the WebView, and loads the mind map data.
*
* @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Otherwise it is null.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mind_map)
Expand Down Expand Up @@ -47,6 +56,12 @@ class MindMapActivity : AppCompatActivity() {
}
}

/**
* Loads the list of notes for a specific book from a JSON file.
*
* @param bookTitle The title of the book for which to load the notes.
* @return A list of Note objects. Returns an empty list if the file doesn't exist or an error occurs.
*/
private fun loadNotes(bookTitle: String): List<Note> {
try {
val file = File(filesDir, "${Utils.sha256(bookTitle)}_notes.json")
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/example/ebookreader/Note.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
package com.example.ebookreader

/**
* Represents a single note taken by the user for a book.
*
* @property text The content of the note.
* @property timestamp The date and time when the note was created, in ISO 8601 format.
*/
data class Note(val text: String, val timestamp: String)
22 changes: 22 additions & 0 deletions app/src/main/java/com/example/ebookreader/NotesActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@ import com.google.gson.reflect.TypeToken
import java.io.File
import java.io.FileReader

/**
* This activity displays a list of notes for a specific book.
* It allows the user to add new notes, view a summary of existing notes, and generate a mind map.
*/
class NotesActivity : AppCompatActivity() {

private lateinit var recyclerView: RecyclerView
private lateinit var notesAdapter: NotesAdapter
private var bookTitle: String? = null

/**
* Initializes the activity, sets up the RecyclerView for notes, and handles UI interactions.
*
* @param savedInstanceState If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in onSaveInstanceState(Bundle). Otherwise it is null.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notes)
Expand Down Expand Up @@ -53,18 +62,31 @@ class NotesActivity : AppCompatActivity() {
}
}

/**
* Called when the activity will start interacting with the user.
* This is a good place to begin animations, open exclusive-access devices (such as the camera), etc.
*/
override fun onResume() {
super.onResume()
loadNotes()
}

/**
* Loads the notes for the current book and updates the RecyclerView.
*/
private fun loadNotes() {
if (bookTitle != null) {
val notes = loadNotesFromFile(bookTitle!!)
notesAdapter.updateNotes(notes)
}
}

/**
* Loads the list of notes for a specific book from a JSON file.
*
* @param bookTitle The title of the book for which to load the notes.
* @return A list of Note objects. Returns an empty list if the file doesn't exist or an error occurs.
*/
private fun loadNotesFromFile(bookTitle: String): List<Note> {
try {
val file = File(filesDir, "${Utils.sha256(bookTitle)}_notes.json")
Expand Down
Loading