diff --git a/pdf-viewer/.gitignore b/pdf-viewer/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/pdf-viewer/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/pdf-viewer/build.gradle b/pdf-viewer/build.gradle
new file mode 100644
index 00000000..2690e7b0
--- /dev/null
+++ b/pdf-viewer/build.gradle
@@ -0,0 +1,40 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 31
+
+ defaultConfig {
+ minSdk 23
+ targetSdk 31
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation 'androidx.appcompat:appcompat:1.4.1'
+ implementation 'com.google.android.material:material:1.5.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}
\ No newline at end of file
diff --git a/pdf-viewer/consumer-rules.pro b/pdf-viewer/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/pdf-viewer/src/main/AndroidManifest.xml b/pdf-viewer/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..2a8167a3
--- /dev/null
+++ b/pdf-viewer/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/extensions/PdfRendereExt.kt b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/extensions/PdfRendereExt.kt
new file mode 100644
index 00000000..bae97ae6
--- /dev/null
+++ b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/extensions/PdfRendereExt.kt
@@ -0,0 +1,24 @@
+package ru.touchin.roboswag.pdf_reader.extensions
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.pdf.PdfRenderer
+
+fun PdfRenderer.Page.renderBitmap(width: Int) = use {
+ val bitmap = createBitmap(width)
+ render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
+ bitmap
+}
+
+private fun PdfRenderer.Page.createBitmap(bitmapWidth: Int): Bitmap {
+ val bitmap = Bitmap.createBitmap(
+ bitmapWidth, (bitmapWidth.toFloat() / width * height).toInt(), Bitmap.Config.ARGB_8888
+ )
+
+ val canvas = Canvas(bitmap)
+ canvas.drawColor(Color.WHITE)
+ canvas.drawBitmap(bitmap, 0f, 0f, null)
+
+ return bitmap
+}
\ No newline at end of file
diff --git a/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/repository/PdfViewRepository.kt b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/repository/PdfViewRepository.kt
new file mode 100644
index 00000000..b1515cfc
--- /dev/null
+++ b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/repository/PdfViewRepository.kt
@@ -0,0 +1,10 @@
+package ru.touchin.roboswag.pdf_reader.repository
+
+import android.graphics.Bitmap
+import java.io.File
+
+interface PdfViewRepository {
+
+ suspend fun renderSinglePage(filePath: String, width: Int) : Bitmap
+
+}
\ No newline at end of file
diff --git a/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/ui/base/BaseFragment.kt b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/ui/base/BaseFragment.kt
new file mode 100644
index 00000000..971dd95b
--- /dev/null
+++ b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/ui/base/BaseFragment.kt
@@ -0,0 +1,58 @@
+package ru.touchin.roboswag.pdf_reader.ui.base
+
+import android.graphics.Bitmap
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import ru.touchin.roboswag.pdf_reader.viewstate.PdfReaderViewState
+import ru.touchin.roboswag.pdf_reader.BaseViewModel
+import java.io.File
+
+abstract class BaseFragment : Fragment() {
+
+ abstract val viewModel: VM
+
+ protected open fun renderData(state: PdfReaderViewState) {
+ when (state) {
+ is PdfReaderViewState.RenderingSuccess -> renderSuccess(state.data)
+ is PdfReaderViewState.Error -> renderError(state.error)
+ is PdfReaderViewState.Loading -> setLoading(true)
+ }
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ viewModel.stateLiveData.observe(viewLifecycleOwner) { state ->
+ renderData(state)
+ }
+
+ }
+
+ protected open fun renderSuccess(bitmap: Bitmap) {
+ setLoading(false)
+ }
+
+ protected open fun renderError(error: Throwable) {
+ setLoading(false)
+ error.message?.let { showMessage(it) }
+ }
+
+ protected open fun renderMessage(message: String) {
+ setLoading(false)
+ showMessage(message)
+ }
+
+ protected open fun setLoading(isLoading: Boolean) {
+ }
+
+ private fun showMessage(message: String) {
+ Toast.makeText(
+ requireContext(),
+ message,
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
+}
\ No newline at end of file
diff --git a/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewmodel/BaseViewModel.kt b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewmodel/BaseViewModel.kt
new file mode 100644
index 00000000..cbebc6f0
--- /dev/null
+++ b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewmodel/BaseViewModel.kt
@@ -0,0 +1,46 @@
+package ru.touchin.roboswag.pdf_reader.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import ru.touchin.roboswag.pdf_reader.viewstate.PdfReaderViewState
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.async
+import kotlinx.coroutines.cancelChildren
+import kotlinx.coroutines.launch
+
+abstract class BaseViewModel(
+) : ViewModel() {
+
+ protected val mStateLiveData = MutableLiveData()
+ val stateLiveData get() = mStateLiveData as LiveData
+
+ private val viewModelCoroutineScope = CoroutineScope(
+ Dispatchers.IO
+ + SupervisorJob()
+ + CoroutineExceptionHandler { _, throwable ->
+ handleError(throwable)
+ })
+
+ override fun onCleared() {
+ super.onCleared()
+ cancelJob()
+ }
+
+ protected open fun cancelJob() {
+ viewModelCoroutineScope.coroutineContext.cancelChildren()
+ }
+
+ private fun handleError(error: Throwable) {
+ mStateLiveData.postValue(PdfReaderViewState.Error(error))
+ }
+
+ protected fun runAsync(block: suspend () -> Unit) =
+ viewModelCoroutineScope.launch {
+ block()
+ }
+
+}
\ No newline at end of file
diff --git a/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewmodel/PdfViewModel.kt b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewmodel/PdfViewModel.kt
new file mode 100644
index 00000000..19cba5b1
--- /dev/null
+++ b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewmodel/PdfViewModel.kt
@@ -0,0 +1,16 @@
+package ru.touchin.roboswag.pdf_reader.viewmodel
+
+import ru.touchin.roboswag.pdf_reader.viewstate.PdfReaderViewState
+import ru.touchin.roboswag.pdf_reader.repository.PdfViewRepository
+
+class PdfViewModel(private val repository: PdfViewRepository) : BaseViewModel() {
+
+ fun renderPage(filePath: String, width: Int) {
+ mStateLiveData.postValue(PdfReaderViewState.Loading(true))
+ runAsync {
+ val bitmap = repository.renderSinglePage(filePath, width)
+ mStateLiveData.postValue(PdfReaderViewState.RenderingSuccess(bitmap))
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewstate/PdfReaderViewState.kt b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewstate/PdfReaderViewState.kt
new file mode 100644
index 00000000..31b2a471
--- /dev/null
+++ b/pdf-viewer/src/main/java/ru/touchin/roboswag/pdf_viewer/viewstate/PdfReaderViewState.kt
@@ -0,0 +1,10 @@
+package ru.touchin.roboswag.pdf_reader.viewstate
+
+import android.graphics.Bitmap
+import java.io.File
+
+sealed class PdfReaderViewState {
+ data class RenderingSuccess(val data: Bitmap) : PdfReaderViewState()
+ data class Error(val error: Throwable) : PdfReaderViewState()
+ data class Loading(val isLoading: Boolean) : PdfReaderViewState()
+}
\ No newline at end of file