Commit 6fcf06a5 by Aleksandr Tamakov

Flow to send print jobs

parent 152ff13e
...@@ -107,6 +107,7 @@ dependencies { ...@@ -107,6 +107,7 @@ dependencies {
implementation(project(":feature:rendering")) implementation(project(":feature:rendering"))
implementation(project(":feature:job")) implementation(project(":feature:job"))
implementation(project(":feature:spot")) implementation(project(":feature:spot"))
implementation(project(":feature:job_sender"))
// implementation(project((":feature:ms_office"))) // implementation(project((":feature:ms_office")))
implementation(project((":library:slider"))) implementation(project((":library:slider")))
implementation(project((":core:core"))) implementation(project((":core:core")))
...@@ -125,9 +126,11 @@ dependencies { ...@@ -125,9 +126,11 @@ dependencies {
implementation("androidx.preference:preference-ktx:${GoogleVersions.preferences}") implementation("androidx.preference:preference-ktx:${GoogleVersions.preferences}")
implementation("androidx.room:room-runtime:${GoogleVersions.room}") implementation("androidx.room:room-runtime:${GoogleVersions.room}")
implementation("androidx.hilt:hilt-work:${GoogleVersions.work_hilt}") implementation("androidx.hilt:hilt-work:${GoogleVersions.work_hilt}")
implementation("androidx.work:work-runtime-ktx:${GoogleVersions.work}")
ksp("com.google.dagger:hilt-compiler:${GoogleVersions.hilt}")
ksp("androidx.room:room-compiler:${GoogleVersions.room}") ksp("androidx.room:room-compiler:${GoogleVersions.room}")
ksp("androidx.hilt:hilt-compiler:${GoogleVersions.work_hilt}")
ksp("com.google.dagger:hilt-compiler:${GoogleVersions.hilt}")
// geo location // geo location
implementation(project(":library:location")) implementation(project(":library:location"))
......
...@@ -48,6 +48,18 @@ ...@@ -48,6 +48,18 @@
<meta-data <meta-data
android:name="com.google.android.geo.API_KEY" android:name="com.google.android.geo.API_KEY"
android:value="AIzaSyBKI2W1CALiYHJcu8KNfYbhTZBpOZZKYPc" /> android:value="AIzaSyBKI2W1CALiYHJcu8KNfYbhTZBpOZZKYPc" />
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
</application> </application>
</manifest> </manifest>
\ No newline at end of file
...@@ -19,13 +19,11 @@ import androidx.compose.ui.graphics.toArgb ...@@ -19,13 +19,11 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.isidroid.c23.data.worker.job.SendJobWorker
import com.isidroid.c23.ext.isEdgeToEdge import com.isidroid.c23.ext.isEdgeToEdge
import com.isidroid.c23.ui.navigation.AppNavHost import com.isidroid.c23.ui.navigation.AppNavHost
import com.isidroid.c23.ui.theme.AppTheme import com.isidroid.c23.ui.theme.AppTheme
import com.isidroid.core.ext.printCurrentDestination import com.isidroid.core.ext.printCurrentDestination
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.util.UUID
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
......
...@@ -7,7 +7,9 @@ import androidx.room.Room ...@@ -7,7 +7,9 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import com.isidroid.job.data.source.local.JobDao import com.isidroid.job.data.source.local.JobDao
import com.isidroid.job_sender.data.source.local.SendJobDao
import com.isidroid.job.model.PrintJob import com.isidroid.job.model.PrintJob
import com.isidroid.job_sender.domain.model.PrintJobSender
import com.isidroid.spot.data.source.local.dao.PrintProfileDao import com.isidroid.spot.data.source.local.dao.PrintProfileDao
import com.isidroid.spot.model.PrintProfile import com.isidroid.spot.model.PrintProfile
import com.isidroid.session.data.source.local.SessionDao import com.isidroid.session.data.source.local.SessionDao
...@@ -16,16 +18,18 @@ import com.isidroid.spot.model.Spot ...@@ -16,16 +18,18 @@ import com.isidroid.spot.model.Spot
import com.isidroid.spot.data.source.local.dao.SpotDao import com.isidroid.spot.data.source.local.dao.SpotDao
@Database( @Database(
version = 2, version = 3,
entities = [ entities = [
Session::class, Session::class,
PrintProfile::class, PrintProfile::class,
Spot::class, Spot::class,
PrintJob::class PrintJob::class,
PrintJobSender::class
], ],
exportSchema = true, exportSchema = true,
autoMigrations = [ autoMigrations = [
AutoMigration(from = 1, to = 2), AutoMigration(from = 1, to = 2),
AutoMigration(from = 2, to = 3),
] ]
) )
@TypeConverters(RoomConverters::class) @TypeConverters(RoomConverters::class)
...@@ -34,6 +38,7 @@ abstract class AppDataBase : RoomDatabase() { ...@@ -34,6 +38,7 @@ abstract class AppDataBase : RoomDatabase() {
abstract val printProfileDao: PrintProfileDao abstract val printProfileDao: PrintProfileDao
abstract val spotDao: SpotDao abstract val spotDao: SpotDao
abstract val jobDao: JobDao abstract val jobDao: JobDao
abstract val sendJobDao: SendJobDao
companion object { companion object {
@Volatile @Volatile
......
package com.isidroid.c23.data.worker.job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
internal class SendJobEventCollectorFlow @Inject constructor() {
private val _eventsFlow = MutableSharedFlow<State>()
val eventsFlow = _eventsFlow.asSharedFlow()
class State {
}
}
\ No newline at end of file
...@@ -23,4 +23,9 @@ object DatabaseModule { ...@@ -23,4 +23,9 @@ object DatabaseModule {
@Provides @Singleton @Provides @Singleton
fun providesJobDao(appDataBase: AppDataBase) = appDataBase.jobDao fun providesJobDao(appDataBase: AppDataBase) = appDataBase.jobDao
@Provides @Singleton
fun providesSendJobDao(appDataBase: AppDataBase) = appDataBase.sendJobDao
} }
\ No newline at end of file
package com.isidroid.c23.domain.use_case package com.isidroid.c23.domain.use_case
import android.content.Context
import com.isidroid.job_sender.SendJobWorker
import com.isidroid.c23.ext.isDebug import com.isidroid.c23.ext.isDebug
import com.isidroid.c23.ui.screen.home.HomeContract import com.isidroid.c23.ui.screen.home.HomeContract
import com.isidroid.core.FlowResult import com.isidroid.core.FlowResult
import com.isidroid.session.repository.SessionRepository import com.isidroid.session.repository.SessionRepository
import com.isidroid.spot.repository.ActiveSpotRepository import com.isidroid.spot.repository.ActiveSpotRepository
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import javax.inject.Inject import javax.inject.Inject
...@@ -12,12 +15,15 @@ import javax.inject.Singleton ...@@ -12,12 +15,15 @@ import javax.inject.Singleton
@Singleton @Singleton
class HomeUseCase @Inject constructor( class HomeUseCase @Inject constructor(
@ApplicationContext private val context: Context,
private val sessionRepository: SessionRepository, private val sessionRepository: SessionRepository,
private val activeSpotRepository: ActiveSpotRepository private val activeSpotRepository: ActiveSpotRepository
) { ) {
fun createSession() = flow { fun createSession() = flow {
emit(FlowResult.Loading) emit(FlowResult.Loading)
SendJobWorker.create(context)
val maxDelay = if (isDebug()) 0 else 3_000 val maxDelay = if (isDebug()) 0 else 3_000
val startedAt = System.currentTimeMillis() val startedAt = System.currentTimeMillis()
......
...@@ -20,7 +20,7 @@ class PrintJobsUseCase @Inject constructor( ...@@ -20,7 +20,7 @@ class PrintJobsUseCase @Inject constructor(
fun load() = flow { fun load() = flow {
emit(FlowResult.Loading) emit(FlowResult.Loading)
val jobList = repository.readLocalList() val jobList = repository.readLocalList()
val spots = jobList.map { it.spotId }.distinct().let { spotRepository.finLocalRichSpots(it) }?.associateBy({ it.spot.id }, { it }) val spots = jobList.map { it.spotId }.distinct().let { spotRepository.findLocalRichSpots(it) }?.associateBy({ it.spot.id }, { it })
val result = jobList.map { job -> val result = jobList.map { job ->
val richSpot = spots?.get(job.spotId) val richSpot = spots?.get(job.spotId)
......
...@@ -6,7 +6,7 @@ import android.net.Uri ...@@ -6,7 +6,7 @@ import android.net.Uri
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import com.isidroid.c23.R import com.isidroid.c23.R
import com.isidroid.c23.SpotHasNoPrintProfilesException import com.isidroid.c23.SpotHasNoPrintProfilesException
import com.isidroid.c23.data.worker.job.SendJobWorker import com.isidroid.job_sender.SendJobWorker
import com.isidroid.c23.ext.transformToBitmapFiles import com.isidroid.c23.ext.transformToBitmapFiles
import com.isidroid.core.FlowResult import com.isidroid.core.FlowResult
import com.isidroid.job.repository.JobRepository import com.isidroid.job.repository.JobRepository
......
...@@ -51,7 +51,7 @@ object NetworkVersions { ...@@ -51,7 +51,7 @@ object NetworkVersions {
object FirebaseDependencies { object FirebaseDependencies {
const val analytics = "com.google.firebase:firebase-analytics-ktx" const val analytics = "com.google.firebase:firebase-analytics-ktx"
const val bom = "33.1.0" const val bom = "33.1.1"
const val crashlytics = "com.google.firebase:firebase-crashlytics-ktx" const val crashlytics = "com.google.firebase:firebase-crashlytics-ktx"
const val messaging = "com.google.firebase:firebase-messaging-ktx" const val messaging = "com.google.firebase:firebase-messaging-ktx"
const val config = "com.google.firebase:firebase-config-ktx" const val config = "com.google.firebase:firebase-config-ktx"
...@@ -69,8 +69,8 @@ object ToolsVersions { ...@@ -69,8 +69,8 @@ object ToolsVersions {
object TestVersions { object TestVersions {
const val junit = "4.13.2" const val junit = "4.13.2"
const val junitExt = "1.1.5" const val junitExt = "1.2.1"
const val espressoCore = "3.5.1" const val espressoCore = "3.6.1"
const val mockitoCore = "5.7.0" const val mockitoCore = "5.7.0"
const val mockitoKotlin = "5.1.0" const val mockitoKotlin = "5.1.0"
const val mockWebServer = "4.12.0" const val mockWebServer = "4.12.0"
......
package com.isidroid.network
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okio.BufferedSink
import timber.log.Timber
import java.io.File
class ProgressEmittingRequestBody(
private val mediaType: String,
private val file: File,
) : RequestBody() {
override fun contentType(): MediaType? = mediaType.toMediaTypeOrNull()
override fun contentLength(): Long = file.length()
override fun writeTo(sink: BufferedSink) {
val buffer = ByteArray(1024)
var uploaded: Long = 0
val inputStream = file.inputStream()
val fileSize = file.length()
try {
while (true) {
val read = inputStream.read(buffer)
if (read == -1) break
uploaded += read
sink.write(buffer, 0, read)
val progress = (((uploaded / fileSize.toDouble())) * 100).toInt()
}
} catch (e: Exception) {
Timber.e(e)
} finally {
inputStream.close()
}
}
}
\ No newline at end of file
...@@ -49,7 +49,6 @@ dependencies { ...@@ -49,7 +49,6 @@ dependencies {
ksp("androidx.room:room-compiler:${GoogleVersions.room}") ksp("androidx.room:room-compiler:${GoogleVersions.room}")
// Firebase // Firebase
api(platform("com.google.firebase:firebase-bom:${FirebaseDependencies.bom}"))
api("com.google.firebase:firebase-crashlytics-ktx") api("com.google.firebase:firebase-crashlytics-ktx")
// tools // tools
......
...@@ -14,6 +14,10 @@ annotation class JobStatus { ...@@ -14,6 +14,10 @@ annotation class JobStatus {
const val PRINTING = 6 const val PRINTING = 6
const val COMPLETED = 7 const val COMPLETED = 7
const val ERROR = 8 const val ERROR = 8
const val IDLE = 0
const val RENDER_UPLOAD = 9
const val READY_TO_PRINT = 10
} }
} }
...@@ -28,5 +32,9 @@ val Int.statusName ...@@ -28,5 +32,9 @@ val Int.statusName
JobStatus.PRINTING -> "PRINTING" JobStatus.PRINTING -> "PRINTING"
JobStatus.COMPLETED -> "COMPLETED" JobStatus.COMPLETED -> "COMPLETED"
JobStatus.ERROR -> "ERROR" JobStatus.ERROR -> "ERROR"
JobStatus.IDLE -> "IDLE"
JobStatus.RENDER_UPLOAD -> "RENDER_UPLOAD"
JobStatus.READY_TO_PRINT -> "READY_TO_PRINT"
else -> "Unknown" else -> "Unknown"
} }
\ No newline at end of file
package com.isidroid.job.constant
@Retention(AnnotationRetention.SOURCE)
annotation class SendJobStatus {
companion object {
const val RENDERED = 0
const val SENDING = 1
}
}
...@@ -16,9 +16,17 @@ interface JobDao { ...@@ -16,9 +16,17 @@ interface JobDao {
@Delete @Delete
fun delete(vararg printJob: PrintJob) fun delete(vararg printJob: PrintJob)
@Query("SELECT * FROM PrintJob WHERE (:spotId IS NULL OR spotId = :spotId) OR (status IS NULL OR status = :status) ORDER BY createdAt DESC") @Query("SELECT * FROM PrintJob " +
"WHERE (:spotId IS NULL OR spotId = :spotId) " +
"AND (:status IS NULL OR status = :status) " +
"AND (:ids IS NULL OR id IN (:ids))" +
"ORDER BY createdAt DESC")
fun find( fun find(
spotId: String? = null, spotId: String? = null,
@JobStatus status: Int? = null @JobStatus status: Int? = null,
ids: List<String>? = null
): List<PrintJob> ): List<PrintJob>
@Query("UPDATE PrintJob SET status = :status WHERE id IN (:ids)")
fun updateJobStatus(status: Int, vararg ids: String)
} }
\ No newline at end of file
...@@ -2,8 +2,9 @@ package com.isidroid.job.data.source.local ...@@ -2,8 +2,9 @@ package com.isidroid.job.data.source.local
import com.isidroid.job.model.PrintJob import com.isidroid.job.model.PrintJob
internal class JobLocalSource(private val dao: JobDao) : JobDao { class JobLocalSource(private val dao: JobDao) : JobDao {
override fun insert(vararg printJob: PrintJob) = dao.insert(*printJob) override fun insert(vararg printJob: PrintJob) = dao.insert(*printJob)
override fun delete(vararg printJob: PrintJob) = dao.delete(*printJob) override fun delete(vararg printJob: PrintJob) = dao.delete(*printJob)
override fun find(spotId: String?, status: Int?) = dao.find(spotId, status) override fun find(spotId: String?, status: Int?, ids: List<String>?) = dao.find(spotId, status, ids)
override fun updateJobStatus(status: Int, vararg ids: String) = dao.updateJobStatus(status, *ids)
} }
\ No newline at end of file
...@@ -4,10 +4,28 @@ import com.isidroid.job.data.mapper.transform ...@@ -4,10 +4,28 @@ import com.isidroid.job.data.mapper.transform
import com.isidroid.job.data.source.remote.api.ApiJob import com.isidroid.job.data.source.remote.api.ApiJob
import com.isidroid.job.data.source.remote.api.request.CreateJobRequest import com.isidroid.job.data.source.remote.api.request.CreateJobRequest
import com.isidroid.job.model.PrintJob import com.isidroid.job.model.PrintJob
import com.isidroid.network.ProgressEmittingRequestBody
import okhttp3.MultipartBody
import java.io.File
internal class JobNetworkSource(private val api: ApiJob) { class JobNetworkSource(private val api: ApiJob) {
fun createJob(spotId: String, profileId: String, clientName: String): PrintJob? { fun createJob(spotId: String, profileId: String, clientName: String): PrintJob? {
val response = api.createJob(CreateJobRequest(spotId, profileId, senderName = clientName)).execute() val response = api.createJob(CreateJobRequest(spotId, profileId, senderName = clientName)).execute()
return response.body()?.transform() return response.body()?.transform()
} }
fun uploadPage(jobId: String, token: String, filePath: String): Boolean {
val file = File(filePath)
val requestBody = ProgressEmittingRequestBody("image/*", file)
val fileBody = MultipartBody.Part.createFormData("file", file.name, requestBody)
val response = api.uploadPage(jobId = jobId, token = token, fileBody).execute()
return response.isSuccessful
}
fun complete(jobId: String, token: String): Boolean {
api.complete(jobId = jobId, token = token).execute()
return true
}
} }
\ No newline at end of file
...@@ -2,11 +2,33 @@ package com.isidroid.job.data.source.remote.api ...@@ -2,11 +2,33 @@ package com.isidroid.job.data.source.remote.api
import com.isidroid.job.data.source.remote.api.request.CreateJobRequest import com.isidroid.job.data.source.remote.api.request.CreateJobRequest
import com.isidroid.job.data.source.remote.api.response.CreateJobResponse import com.isidroid.job.data.source.remote.api.response.CreateJobResponse
import okhttp3.MultipartBody
import okhttp3.ResponseBody
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.PATCH
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
internal interface ApiJob { interface ApiJob {
@POST("api/job") @POST("api/job")
fun createJob(@Body request: CreateJobRequest): Call<CreateJobResponse> fun createJob(@Body request: CreateJobRequest): Call<CreateJobResponse>
@Multipart
@POST("api/job/{jobId}")
fun uploadPage(
@Path("jobId") jobId: String,
@Header("X-Access-Token") token: String,
@Part body: MultipartBody.Part
): Call<ResponseBody>
@PATCH("api/job/{jobId}")
fun complete(
@Path("jobId") jobId: String,
@Header("X-Access-Token") token: String,
): Call<ResponseBody>
} }
\ No newline at end of file
...@@ -2,7 +2,7 @@ package com.isidroid.job.data.source.remote.api.request ...@@ -2,7 +2,7 @@ package com.isidroid.job.data.source.remote.api.request
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
internal data class CreateJobRequest( data class CreateJobRequest(
@SerializedName("spot_id") val spotId: String, @SerializedName("spot_id") val spotId: String,
@SerializedName("profile_id") val profileId: String, @SerializedName("profile_id") val profileId: String,
@SerializedName("sender_name") val senderName: String, @SerializedName("sender_name") val senderName: String,
......
...@@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName ...@@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName
import java.io.Serial import java.io.Serial
import java.util.Date import java.util.Date
internal data class CreateJobResponse( data class CreateJobResponse(
@SerializedName("id") val id: String, @SerializedName("id") val id: String,
@SerializedName("access_code") val accessCode: String, @SerializedName("access_code") val accessCode: String,
@SerializedName("access_token") val accessToken: String, @SerializedName("access_token") val accessToken: String,
......
...@@ -4,8 +4,8 @@ import android.content.Context ...@@ -4,8 +4,8 @@ import android.content.Context
import com.google.gson.Gson import com.google.gson.Gson
import com.isidroid.core.DiDebuggableBuild import com.isidroid.core.DiDebuggableBuild
import com.isidroid.core.DiMock import com.isidroid.core.DiMock
import com.isidroid.job.data.source.local.JobDao
import com.isidroid.job.data.source.local.JobLocalSource import com.isidroid.job.data.source.local.JobLocalSource
import com.isidroid.job.data.source.local.JobDao
import com.isidroid.job.data.source.remote.JobNetworkSource import com.isidroid.job.data.source.remote.JobNetworkSource
import com.isidroid.job.data.source.remote.MockInterceptor import com.isidroid.job.data.source.remote.MockInterceptor
import com.isidroid.job.data.source.remote.api.ApiJob import com.isidroid.job.data.source.remote.api.ApiJob
...@@ -35,12 +35,13 @@ internal object JobModule { ...@@ -35,12 +35,13 @@ internal object JobModule {
fun provideJobLocalSource(dao: JobDao) = JobLocalSource(dao) fun provideJobLocalSource(dao: JobDao) = JobLocalSource(dao)
@Provides @Singleton @Provides @Singleton
fun provideJobRepository( fun provideJobRepository(
@ApplicationContext context: Context, @ApplicationContext context: Context,
jobNetworkSource: JobNetworkSource, jobNetworkSource: JobNetworkSource,
jobLocalSource: JobLocalSource, jobLocalSource: JobLocalSource,
): JobRepository = JobRepositoryImpl(context, jobNetworkSource, jobLocalSource) ): JobRepository = JobRepositoryImpl(jobNetworkSource, jobLocalSource)
@Provides @DiJobMockInterceptor @Provides @DiJobMockInterceptor
fun provideMockInterceptor( fun provideMockInterceptor(
......
...@@ -10,13 +10,13 @@ import java.util.UUID ...@@ -10,13 +10,13 @@ import java.util.UUID
@Entity(indices = [Index("spotId"), Index("status")]) @Entity(indices = [Index("spotId"), Index("status")])
data class PrintJob( data class PrintJob(
@PrimaryKey val id: String = UUID.randomUUID().toString(), @PrimaryKey val id: String = UUID.randomUUID().toString(),
val spotId: String, val spotId: String = "",
val cost: Float = 0f, val cost: Float = 0f,
val profileId: String, val profileId: String = "",
val printSize: Int = -1, val printSize: Int = -1,
val printOrientation: Int = -1, val printOrientation: Int = -1,
val copies: Int = 1, val copies: Int = 1,
@JobStatus val status: Int, @JobStatus val status: Int = JobStatus.IDLE,
val name: String = "", val name: String = "",
val errorMessage: String? = null, val errorMessage: String? = null,
val comment: String? = null, val comment: String? = null,
......
package com.isidroid.job.repository package com.isidroid.job.repository
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.model.PrintJob import com.isidroid.job.model.PrintJob
import java.io.File import java.io.File
...@@ -15,5 +16,8 @@ interface JobRepository { ...@@ -15,5 +16,8 @@ interface JobRepository {
sourceFiles: List<File> sourceFiles: List<File>
): PrintJob ): PrintJob
suspend fun readLocalList(): List<PrintJob> suspend fun readLocalList(spotId: String? = null, @JobStatus status: Int? = null, ids: List<String>? = null): Collection<PrintJob>
suspend fun updateJobStatus(@JobStatus status: Int, vararg ids: String)
suspend fun uploadPage(jobId: String, token: String, filePath: String): Boolean
suspend fun completeUpload(jobId: String, token: String): PrintJob?
} }
\ No newline at end of file
package com.isidroid.job.repository package com.isidroid.job.repository
import android.content.Context
import com.isidroid.job.CreateJobException import com.isidroid.job.CreateJobException
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.data.source.local.JobLocalSource import com.isidroid.job.data.source.local.JobLocalSource
import com.isidroid.job.data.source.remote.JobNetworkSource import com.isidroid.job.data.source.remote.JobNetworkSource
import com.isidroid.job.model.PrintJob import com.isidroid.job.model.PrintJob
import java.io.File import java.io.File
internal class JobRepositoryImpl( internal class JobRepositoryImpl(
private val context: Context,
private val jobNetworkSource: JobNetworkSource, private val jobNetworkSource: JobNetworkSource,
private val jobLocalSource: JobLocalSource, private val jobLocalSource: JobLocalSource,
) : JobRepository { ) : JobRepository {
...@@ -31,8 +30,13 @@ internal class JobRepositoryImpl( ...@@ -31,8 +30,13 @@ internal class JobRepositoryImpl(
return job return job
} }
override suspend fun readLocalList(): List<PrintJob> { override suspend fun readLocalList(spotId: String?, status: Int?, ids: List<String>?) = jobLocalSource.find(spotId = spotId, status = status, ids = ids)
return jobLocalSource.find() override suspend fun updateJobStatus(status: Int, vararg ids: String) = jobLocalSource.updateJobStatus(status, *ids)
} override suspend fun uploadPage(jobId: String, token: String, filePath: String) = jobNetworkSource.uploadPage(jobId = jobId, token = token, filePath = filePath)
override suspend fun completeUpload(jobId: String, token: String): PrintJob? {
val printJob = jobLocalSource.find(ids = listOf(jobId)).firstOrNull()
val uploadResult = jobNetworkSource.complete(jobId = jobId, token = token)
return printJob?.copy(status = JobStatus.READY_TO_PRINT)?.also { jobLocalSource.insert(it) }?.takeIf { uploadResult }
}
} }
\ No newline at end of file
...@@ -6,7 +6,6 @@ import com.isidroid.job.data.source.local.JobDao ...@@ -6,7 +6,6 @@ import com.isidroid.job.data.source.local.JobDao
import com.isidroid.job.data.source.local.JobLocalSource import com.isidroid.job.data.source.local.JobLocalSource
import com.isidroid.job.data.source.remote.JobNetworkSource import com.isidroid.job.data.source.remote.JobNetworkSource
import com.isidroid.job.data.source.remote.api.ApiJob import com.isidroid.job.data.source.remote.api.ApiJob
import com.isidroid.job.repository.JobRepository
import com.isidroid.job.repository.JobRepositoryImpl import com.isidroid.job.repository.JobRepositoryImpl
import com.isidroid.test_utils.MockWebServerWrapper import com.isidroid.test_utils.MockWebServerWrapper
import com.isidroid.test_utils.createApi import com.isidroid.test_utils.createApi
...@@ -19,7 +18,6 @@ import org.junit.Before ...@@ -19,7 +18,6 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
...@@ -49,7 +47,7 @@ class TestPrintJob { ...@@ -49,7 +47,7 @@ class TestPrintJob {
networkSource = JobNetworkSource(api) networkSource = JobNetworkSource(api)
localSource = JobLocalSource(jobDao) localSource = JobLocalSource(jobDao)
jobRepository = JobRepositoryImpl(mockContext, networkSource, localSource) jobRepository = JobRepositoryImpl(networkSource, localSource)
} }
......
/build
\ No newline at end of file
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
}
android {
namespace = "com.isidroid.job"
compileSdk = BuildVersions.COMPILE_SDK
defaultConfig {
minSdk = BuildVersions.MIN_SDK
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
}
dependencies {
api(project(":core:network"))
api(project((":core:core")))
api(project((":core:utils")))
api(project(":feature:rendering"))
implementation(project(":feature:job"))
api(project(":feature:spot"))
testApi(project((":core:test_utils")))
api("androidx.core:core-ktx:${GoogleVersions.coreKtx}")
api("androidx.appcompat:appcompat:${GoogleVersions.appCompat}")
api("com.google.dagger:hilt-android:${GoogleVersions.hilt}")
api("androidx.work:work-runtime-ktx:${GoogleVersions.work}")
api("androidx.hilt:hilt-work:${GoogleVersions.work_hilt}")
api("androidx.room:room-runtime:${GoogleVersions.room}")
ksp("androidx.hilt:hilt-compiler:${GoogleVersions.work_hilt}")
ksp("com.google.dagger:hilt-compiler:${GoogleVersions.hilt}")
ksp("androidx.room:room-compiler:${GoogleVersions.room}")
// Firebase
api("com.google.firebase:firebase-crashlytics-ktx")
// tools
api("com.jakewharton.timber:timber:${ToolsVersions.timber}")
testImplementation("junit:junit:${TestVersions.junit}")
androidTestImplementation("androidx.test.ext:junit:${TestVersions.junitExt}")
androidTestImplementation("androidx.test.espresso:espresso-core:${TestVersions.espressoCore}")
}
\ No newline at end of file
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
\ No newline at end of file
package com.isidroid.job_sender
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.isidroid.job_sender.test", appContext.packageName)
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
\ No newline at end of file
package com.isidroid.job_sender
class RenderBitmapProfileException(m: String? = null): Throwable(m)
class UploadPageException(m: String? = null): Throwable(m)
\ No newline at end of file
package com.isidroid.job_sender
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.model.PrintJob
import com.isidroid.job_sender.domain.dto.JobSenderResult
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.buffer
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SendJobEventCollectorFlow @Inject constructor() {
private val _eventsFlow = MutableSharedFlow<JobSenderResult>(replay = 1)
val eventsFlow = _eventsFlow.asSharedFlow().buffer(capacity = 10)
private suspend fun emit(result: JobSenderResult) = _eventsFlow.emit(result)
.also { Timber.i("call: $result") }
suspend fun renderProgress(index: Int, total: Int) = emit(JobSenderResult.RenderProgress(index, total))
suspend fun updateProgress(index: Int, total: Int) = emit(JobSenderResult.UploadProgress(index, total))
suspend fun jobError(jobId: String, t: Throwable) = emit(JobSenderResult.Error(jobId, t))
suspend fun updateStatus(@JobStatus status: Int, vararg job: PrintJob?) {
val jobList = job.toList().filterNotNull()
if (jobList.isNotEmpty())
emit(JobSenderResult.Statuses(status = status, jobs = jobList))
}
}
\ No newline at end of file
package com.isidroid.c23.data.worker.job package com.isidroid.job_sender
import android.content.Context import android.content.Context
import androidx.hilt.work.HiltWorker import androidx.hilt.work.HiltWorker
...@@ -10,27 +10,43 @@ import androidx.work.NetworkType ...@@ -10,27 +10,43 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.isidroid.job_sender.domain.use_case.SendPrintJobsUseCase
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject
@HiltWorker @HiltWorker
internal class SendJobWorker @AssistedInject constructor( class SendJobWorker @AssistedInject constructor(
@Assisted appContext: Context, @Assisted appContext: Context,
@Assisted workerParams: WorkerParameters, @Assisted workerParams: WorkerParameters,
// @Inject private val collectorFlow: SendJobEventCollectorFlow private val useCase: SendPrintJobsUseCase,
private val collector: SendJobEventCollectorFlow
) : CoroutineWorker(appContext, workerParams) { ) : CoroutineWorker(appContext, workerParams) {
override suspend fun doWork(): Result = coroutineScope { override suspend fun doWork(): Result = coroutineScope {
val collectorJob = launch { runCollector() }
try {
useCase.start()
Result.success() Result.success()
} catch (t: Throwable) {
Timber.e(t)
Result.failure()
} finally {
collectorJob.cancel()
}
}
private suspend fun runCollector() {
coroutineScope {
collector.eventsFlow.collect {
}
}
} }
companion object { companion object {
...@@ -49,7 +65,7 @@ internal class SendJobWorker @AssistedInject constructor( ...@@ -49,7 +65,7 @@ internal class SendJobWorker @AssistedInject constructor(
.setId(UUID.randomUUID()) .setId(UUID.randomUUID())
// We use KEEP to avoid duplicate runs of SyncRepository // We use KEEP to avoid duplicate runs of SyncRepository
workManager.beginUniqueWork(SendJobWorker::class.java.simpleName, ExistingWorkPolicy.KEEP, requestBuilder.build()).enqueue() workManager.beginUniqueWork(SendJobWorker::class.java.simpleName, ExistingWorkPolicy.REPLACE, requestBuilder.build()).enqueue()
} }
} }
} }
\ No newline at end of file
package com.isidroid.job_sender.data.source.local
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.isidroid.job_sender.domain.model.PrintJobSender
@Dao
interface SendJobDao {
@Query("SELECT * FROM PrintJobSender WHERE id = :id AND printJobId = :printJobId")
fun find(id: String, printJobId: String): PrintJobSender?
@Query("SELECT * FROM PrintJobSender WHERE status = :status")
fun findByStatus(status: Int): List<PrintJobSender>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg item: PrintJobSender)
}
\ No newline at end of file
package com.isidroid.job_sender.data.source.local
import com.isidroid.job.constant.SendJobStatus
import com.isidroid.job_sender.domain.model.PrintJobSender
class SendJobLocalSource(private val dao: SendJobDao) : SendJobDao {
override fun find(id: String, printJobId: String): PrintJobSender? = dao.find(id, printJobId)
override fun findByStatus(status: Int) = dao.findByStatus(status)
override fun insert(vararg item: PrintJobSender) = dao.insert(*item)
fun updateStatus(item: PrintJobSender, @SendJobStatus status: Int) = item.copy(status = status).also { insert(it) }
}
\ No newline at end of file
package com.isidroid.job_sender.di
import com.isidroid.job.repository.JobRepository
import com.isidroid.job_sender.SendJobEventCollectorFlow
import com.isidroid.job_sender.data.source.local.SendJobDao
import com.isidroid.job_sender.data.source.local.SendJobLocalSource
import com.isidroid.job_sender.repository.JobSendRepository
import com.isidroid.job_sender.repository.JobSendRepositoryImpl
import com.isidroid.rendering.repository.RenderRepository
import com.isidroid.spot.repository.SpotRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
object JobSenderModule {
@Provides @Singleton
fun provideSendJobLocalSource(dao: SendJobDao) = SendJobLocalSource(dao)
@Provides @Singleton
fun provideJobSendRepository(
renderRepository: RenderRepository,
spotRepository: SpotRepository,
printJobRepository: JobRepository,
sendJobLocalSource: SendJobLocalSource,
eventCollector: SendJobEventCollectorFlow,
): JobSendRepository = JobSendRepositoryImpl(renderRepository, spotRepository, printJobRepository, sendJobLocalSource, eventCollector)
}
\ No newline at end of file
package com.isidroid.job_sender.domain.dto
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.model.PrintJob
sealed interface JobSenderResult {
data class RenderProgress(val position: Int, val total: Int) : JobSenderResult
data class UploadProgress(val position: Int, val total: Int) : JobSenderResult
data class Error(val jobId: String, val t: Throwable) : JobSenderResult
data class Statuses(@JobStatus val status: Int, val jobs: Collection<PrintJob>): JobSenderResult
}
\ No newline at end of file
package com.isidroid.job_sender.domain.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.isidroid.job.constant.SendJobStatus
import java.util.Date
import java.util.UUID
@Entity
data class PrintJobSender(
@PrimaryKey val id: String = UUID.randomUUID().toString(),
val printJobId: String = "",
@SendJobStatus val status: Int,
val sourceFile: String = "",
val accessToken: String = "",
val createdAt: Date = Date(),
val updatedAt: Date = Date(),
)
\ No newline at end of file
package com.isidroid.job_sender.domain.use_case
import com.isidroid.core.DiMock
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.constant.SendJobStatus
import com.isidroid.job.repository.JobRepository
import com.isidroid.job_sender.SendJobEventCollectorFlow
import com.isidroid.job_sender.data.source.local.SendJobLocalSource
import com.isidroid.job_sender.ext.createRenderItems
import com.isidroid.job_sender.repository.JobSendRepository
import com.isidroid.rendering.repository.RenderRepository
import com.isidroid.spot.repository.SpotRepository
import kotlinx.coroutines.delay
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SendPrintJobsUseCase @Inject constructor(
private val repository: JobSendRepository,
@DiMock private val debug: Boolean
) {
suspend fun start() {
repository.checkNotRenderedPrintJobs(repository.getJobList(JobStatus.IDLE))
repository.readFilesAndSend(repository.getJobSenderList(SendJobStatus.RENDERED))
repository.markJobUploaded(repository.getJobList(JobStatus.RENDER_UPLOAD))
}
}
\ No newline at end of file
package com.isidroid.job_sender.ext
internal fun MutableMap<String, Int>.decreaseCounter(key: String) {
val current = get(key) ?: return
put(key, current - 1)
}
\ No newline at end of file
package com.isidroid.job_sender.ext
import android.graphics.BitmapFactory
import com.isidroid.job.constant.SendJobStatus
import com.isidroid.job.model.PrintJob
import com.isidroid.job_sender.RenderBitmapProfileException
import com.isidroid.job_sender.SendJobEventCollectorFlow
import com.isidroid.job_sender.domain.model.PrintJobSender
import com.isidroid.rendering.model.RenderSettingsV2
import com.isidroid.rendering.repository.RenderRepository
import com.isidroid.spot.model.PrintProfile
import com.isidroid.spot.model.RichSpot
import com.isidroid.spot.repository.SpotRepository
import com.isidroid.utils.md5
import com.isidroid.utils.saveToFile
import java.io.File
import java.util.UUID
internal fun renderBitmapForPrint(filePath: String, printProfile: PrintProfile?, printJob: PrintJob, renderRepository: RenderRepository): File {
printProfile ?: throw RenderBitmapProfileException("Print profile is null")
val picture = BitmapFactory.decodeFile(filePath)
val file = File.createTempFile(UUID.randomUUID().toString(), ".jpg")
val updatedSettings = RenderSettingsV2(
filePath = file.absolutePath,
greyscale = printProfile.grayscale,
isRealSize = true,
orientation = printJob.printOrientation,
printSize = printJob.printSize,
)
val result = renderRepository.renderBitmap(
width = printProfile.width,
height = printProfile.height,
printWidth = printProfile.printWidth,
printHeight = printProfile.printHeight,
marginTop = printProfile.marginTop,
marginLeft = printProfile.marginLeft,
dpix = printProfile.dpix,
dpiy = printProfile.dpiy,
renderSettingsV2 = updatedSettings,
picture = picture,
)
result.saveToFile(file)
return file
}
internal fun createSingleRender(renderRepository: RenderRepository, richSpots: List<RichSpot>?, printJob: PrintJob, source: String): PrintJobSender? {
val printJobId = printJob.id
val richSpot = richSpots?.find { it.spot.id == printJob.spotId } ?: return null
val printProfile = richSpot.printProfiles.find { it.id == printJob.profileId } ?: return null
val id = "${source}_${printJobId}".md5
val file = renderBitmapForPrint(
filePath = source,
printProfile = printProfile,
printJob = printJob,
renderRepository = renderRepository
)
return PrintJobSender(
id = id,
printJobId = printJobId,
status = SendJobStatus.RENDERED,
sourceFile = file.absolutePath,
accessToken = printJob.accessToken.orEmpty(),
)
}
internal suspend fun createRenderItems(
spotRepository: SpotRepository,
renderRepository: RenderRepository,
eventCollector: SendJobEventCollectorFlow,
jobIdleList: Collection<PrintJob>
): Collection<PrintJobSender> {
val richSpots = spotRepository.findLocalRichSpots(ids = jobIdleList.map { it.spotId })
var index = 0
val total = jobIdleList.size
val result = jobIdleList.mapNotNull { printJob ->
printJob.sourceFiles?.mapNotNull { source ->
createSingleRender(renderRepository, richSpots, printJob, source)
.also { eventCollector.renderProgress(++index, total) }
}
}.flatten()
return result
}
\ No newline at end of file
package com.isidroid.job_sender.repository
import com.isidroid.job.model.PrintJob
import com.isidroid.job_sender.domain.model.PrintJobSender
interface JobSendRepository {
suspend fun getJobList(status: Int): Collection<PrintJob>
suspend fun getJobSenderList(status: Int): Collection<PrintJobSender>
suspend fun checkNotRenderedPrintJobs(items: Collection<PrintJob>): Collection<PrintJobSender>?
suspend fun readFilesAndSend(items: Collection<PrintJobSender>): Collection<PrintJob>
suspend fun markJobUploaded(items: Collection<PrintJob>): Collection<PrintJob>
}
\ No newline at end of file
package com.isidroid.job_sender.repository
import com.isidroid.job.constant.JobStatus
import com.isidroid.job.model.PrintJob
import com.isidroid.job.repository.JobRepository
import com.isidroid.job_sender.SendJobEventCollectorFlow
import com.isidroid.job_sender.UploadPageException
import com.isidroid.job_sender.data.source.local.SendJobLocalSource
import com.isidroid.job_sender.domain.model.PrintJobSender
import com.isidroid.job_sender.ext.createRenderItems
import com.isidroid.job_sender.ext.decreaseCounter
import com.isidroid.rendering.repository.RenderRepository
import com.isidroid.spot.repository.SpotRepository
import kotlinx.coroutines.delay
import timber.log.Timber
internal class JobSendRepositoryImpl(
private val renderRepository: RenderRepository,
private val spotRepository: SpotRepository,
private val printJobRepository: JobRepository,
private val sendJobLocalSource: SendJobLocalSource,
private val eventCollector: SendJobEventCollectorFlow,
) : JobSendRepository {
override suspend fun getJobList(status: Int): Collection<PrintJob> = printJobRepository.readLocalList(status = status)
override suspend fun getJobSenderList(status: Int): Collection<PrintJobSender> = sendJobLocalSource.findByStatus(status)
// let's check whether there is not rendered print jobs
override suspend fun checkNotRenderedPrintJobs(items: Collection<PrintJob>): Collection<PrintJobSender>? {
if (items.isNotEmpty()) {
val renderItems = createRenderItems(spotRepository, renderRepository, eventCollector, items)
val jobIds = renderItems.map { it.printJobId }.distinct()
printJobRepository.updateJobStatus(JobStatus.CREATED, *jobIds.toTypedArray())
sendJobLocalSource.insert(*renderItems.toTypedArray())
return renderItems
}
return null
}
// get all pending jobs and send them one by one
override suspend fun readFilesAndSend(items: Collection<PrintJobSender>): Collection<PrintJob> {
val total = items.size
val sendJobResults = items.groupBy { it.printJobId }.mapValues { it.value.size }.toMutableMap()
for ((index, item) in items.withIndex()) {
eventCollector.updateProgress(index, total)
try {
val uploadResult = printJobRepository.uploadPage(jobId = item.printJobId, token = item.accessToken, filePath = item.sourceFile)
if (!uploadResult)
throw UploadPageException()
sendJobResults.decreaseCounter(item.printJobId)
} catch (t: Throwable) {
Timber.e(t)
eventCollector.jobError(jobId = item.printJobId, t = t)
}
}
eventCollector.updateProgress(total, total)
// update jobs
val jobsIds = sendJobResults.filter { it.value == 0 }.keys
printJobRepository.updateJobStatus(status = JobStatus.RENDER_UPLOAD, *jobsIds.toTypedArray())
val jobs = printJobRepository.readLocalList(ids = jobsIds.toList())
// notify event emitters
eventCollector.updateStatus(status = JobStatus.RENDER_UPLOAD, *jobs.toTypedArray())
return jobs
}
override suspend fun markJobUploaded(items: Collection<PrintJob>): Collection<PrintJob> {
return items.mapNotNull { job ->
// upload
val updateJob = printJobRepository.completeUpload(jobId = job.id, token = job.accessToken.orEmpty())
// notify event emitters
eventCollector.updateStatus(status = JobStatus.READY_TO_PRINT, updateJob)
updateJob
}
}
}
package com.isidroid.job_sender
import org.junit.Test
import org.junit.Assert.*
import java.util.UUID
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
val items = listOf(
Item(parentId = "1"),
Item(parentId = "1"),
Item(parentId = "2"),
)
val data: Map<String, Int> = items.groupBy { it.parentId }.mapValues { it.value.size }
println("$data")
}
}
data class Item(val parentId: String, val id: String = UUID.randomUUID().toString())
\ No newline at end of file
...@@ -16,7 +16,8 @@ internal fun createRenderBitmap( ...@@ -16,7 +16,8 @@ internal fun createRenderBitmap(
bitmapConfig: Bitmap.Config bitmapConfig: Bitmap.Config
): Bitmap { ): Bitmap {
val noMargins = settings.printSize in arrayOf(PrintSize.FIT_TO_PAPER, PrintSize.FILL_PAGE) val noMargins = settings.printSize in arrayOf(PrintSize.FIT_TO_PAPER, PrintSize.FILL_PAGE)
val hasBorders = settings.showPrintableArea //settings.printSize in arrayOf(PrintSize.FIT_TO_PAPER, PrintSize.FILL_PAGE, PrintSize.PAPER_ORIGINAL_PAGE) val hasBorders =
settings.showPrintableArea //settings.printSize in arrayOf(PrintSize.FIT_TO_PAPER, PrintSize.FILL_PAGE, PrintSize.PAPER_ORIGINAL_PAGE)
val scaledCanvasSize = getScaledPrintCanvas( val scaledCanvasSize = getScaledPrintCanvas(
filePath = settings.filePath, filePath = settings.filePath,
...@@ -25,7 +26,7 @@ internal fun createRenderBitmap( ...@@ -25,7 +26,7 @@ internal fun createRenderBitmap(
printCanvas = printCanvas, printCanvas = printCanvas,
) )
val scaleFactor = settings.canvasWidth / scaledCanvasSize.width val scaleFactor = if (settings.canvasWidth > 0) settings.canvasWidth / scaledCanvasSize.width else 1f
val scaledCanvas2 = scaledCanvasSize.copy( val scaledCanvas2 = scaledCanvasSize.copy(
width = scaleFactor * scaledCanvasSize.width, width = scaleFactor * scaledCanvasSize.width,
height = scaleFactor * scaledCanvasSize.height, height = scaleFactor * scaledCanvasSize.height,
......
...@@ -9,8 +9,8 @@ data class RenderSettingsV2( ...@@ -9,8 +9,8 @@ data class RenderSettingsV2(
val isRealSize: Boolean = false, val isRealSize: Boolean = false,
val filePath: String = "", val filePath: String = "",
@PrintOrientation val orientation: Int = PrintOrientation.AUTO, val orientation: Int = PrintOrientation.AUTO,
@PrintSize val printSize: Int = PrintSize.FIT_TO_PAPER, val printSize: Int = PrintSize.FIT_TO_PAPER,
val copies: Int = 1, val copies: Int = 1,
val canvasWidth: Float = 0f, val canvasWidth: Float = 0f,
......
...@@ -7,14 +7,14 @@ import com.isidroid.rendering.model.RenderSettingsV2 ...@@ -7,14 +7,14 @@ import com.isidroid.rendering.model.RenderSettingsV2
interface RenderRepository { interface RenderRepository {
fun renderBitmap( fun renderBitmap(
width: Int, width: Int = 0,
height: Int, height: Int= 0,
printWidth: Int, printWidth: Int= 0,
printHeight: Int, printHeight: Int= 0,
marginTop: Int, marginTop: Int= 0,
marginLeft: Int, marginLeft: Int= 0,
dpix: Int, dpix: Int= 0,
dpiy: Int, dpiy: Int= 0,
renderSettingsV2: RenderSettingsV2, renderSettingsV2: RenderSettingsV2,
picture: Bitmap picture: Bitmap
): Bitmap ): Bitmap
......
...@@ -6,13 +6,13 @@ import androidx.room.PrimaryKey ...@@ -6,13 +6,13 @@ import androidx.room.PrimaryKey
@Entity(indices = [Index("code")]) @Entity(indices = [Index("code")])
data class Spot( data class Spot(
@PrimaryKey val id: String, @PrimaryKey val id: String = "",
val name: String, val name: String = "",
val description: String? = null, val description: String? = null,
val code: String, val code: String = "",
val address: String, val address: String = "",
val status: String?, val status: String? = null,
val distance: Float, val distance: Float = 0f,
val lng: Double, val lng: Double = 0.0,
val lat: Double, val lat: Double = 0.0,
) )
\ No newline at end of file
...@@ -5,6 +5,6 @@ import com.isidroid.spot.model.RichSpot ...@@ -5,6 +5,6 @@ import com.isidroid.spot.model.RichSpot
interface SpotRepository { interface SpotRepository {
suspend fun locateSpots(lat: Double, lng: Double, distance: Int): List<RichSpot> suspend fun locateSpots(lat: Double, lng: Double, distance: Int): List<RichSpot>
suspend fun findRichSpot(code: String?): RichSpot? suspend fun findRichSpot(code: String?): RichSpot?
suspend fun finLocalRichSpots(ids: List<String>): List<RichSpot>? suspend fun findLocalRichSpots(ids: List<String>): List<RichSpot>?
suspend fun save(richSpot: RichSpot?) suspend fun save(richSpot: RichSpot?)
} }
\ No newline at end of file
...@@ -19,7 +19,7 @@ internal class SpotRepositoryImpl( ...@@ -19,7 +19,7 @@ internal class SpotRepositoryImpl(
override suspend fun findRichSpot(code: String?): RichSpot? = spotNetworkSource.find(code) override suspend fun findRichSpot(code: String?): RichSpot? = spotNetworkSource.find(code)
override suspend fun finLocalRichSpots(ids: List<String>): List<RichSpot>? { override suspend fun findLocalRichSpots(ids: List<String>): List<RichSpot>? {
val spots = spotLocalSource.findSpots(ids) val spots = spotLocalSource.findSpots(ids)
val profiles = printProfileLocalSource.findProfilesInSpots(ids).convertToMap() val profiles = printProfileLocalSource.findProfilesInSpots(ids).convertToMap()
return spots.transformToRichSpot(profiles) return spots.transformToRichSpot(profiles)
......
...@@ -30,3 +30,4 @@ include(":ui:maps") ...@@ -30,3 +30,4 @@ include(":ui:maps")
include(":feature:ms_office") include(":feature:ms_office")
include(":feature:job") include(":feature:job")
include(":ui:print_job") include(":ui:print_job")
include(":feature:job_sender")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment