Compare commits
	
		
			17 Commits
		
	
	
		
			v172103081
			...
			v172110299
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ae32cbfb6f | |||
| 2dff3d9191 | |||
| c0ae0466c2 | |||
| 58b0574cf9 | |||
| 5472c607cd | |||
| f95cb20408 | |||
| 5640b7e56c | |||
| fa697f1313 | |||
| a12623f8e4 | |||
| abba04839a | |||
| 2e38639910 | |||
|  | db78717eec | ||
| 58a498868d | |||
| 46e723a238 | |||
|  | 304b6c3761 | ||
| 33fb04956c | |||
| 6e3381fb61 | 
| @@ -42,6 +42,8 @@ | ||||
|  | ||||
| - Closing #322. App crashed because of svg images. | ||||
|  | ||||
| - Closing #236. New sources can be added in Selfoss 2.19. | ||||
|  | ||||
| **1.6.x** | ||||
|  | ||||
| - Handling hidden tags. | ||||
|   | ||||
| @@ -36,3 +36,12 @@ If you are a user, you can still create new issues. I'll fix them when I can. | ||||
| - [See what I'm doing](https://github.com/aminecmi/ReaderforSelfoss/projects/1) | ||||
| - [Create an issue, or request a new feature](https://github.com/aminecmi/ReaderforSelfoss/issues) | ||||
| - [Help translation the app](https://crowdin.com/project/readerforselfoss) | ||||
|  | ||||
| ## Contributors (Alphabetical order) ❤️ | ||||
|  | ||||
| - [@aancel](https://github.com/aancel) | ||||
| - [@Binnette](https://github.com/Binnette) | ||||
| - [@davidoskky](https://github.com/davidoskky) | ||||
| - [@hectorgabucio](https://github.com/hectorgabucio) | ||||
| - [@licaon-kter](https://github.com/licaon-kter) | ||||
| - [@sergey-babkin](https://github.com/sergey-babkin) | ||||
|   | ||||
| @@ -35,15 +35,15 @@ android { | ||||
|         sourceCompatibility JavaVersion.VERSION_1_8 | ||||
|         targetCompatibility JavaVersion.VERSION_1_8 | ||||
|     } | ||||
|     compileSdkVersion 30 | ||||
|     buildToolsVersion '30.0.3' | ||||
|     compileSdkVersion 31 | ||||
|     buildToolsVersion '31.0.0' | ||||
|     buildFeatures { | ||||
|         viewBinding true | ||||
|     } | ||||
|     defaultConfig { | ||||
|         applicationId "apps.amine.bou.readerforselfoss" | ||||
|         minSdkVersion 16 | ||||
|         targetSdkVersion 30 | ||||
|         targetSdkVersion 31 | ||||
|         versionCode versionCodeFromGit() | ||||
|         versionName versionNameFromGit() | ||||
|  | ||||
| @@ -68,11 +68,14 @@ android { | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled true | ||||
|             shrinkResources false | ||||
|             shrinkResources true | ||||
|             proguardFiles getDefaultProguardFile('proguard-android.txt'), | ||||
|                     'proguard-rules.pro' | ||||
|         } | ||||
|         debug { | ||||
|             buildConfigField "String", "LOGIN_URL", appLoginUrl | ||||
|             buildConfigField "String", "LOGIN_PASSWORD", appLoginPassword | ||||
|             buildConfigField "String", "LOGIN_USERNAME", appLoginUsername | ||||
|         } | ||||
|     } | ||||
|     flavorDimensions "build" | ||||
| @@ -82,6 +85,9 @@ android { | ||||
|             dimension "build" | ||||
|         } | ||||
|     } | ||||
|     kotlinOptions { | ||||
|         jvmTarget = '1.8' | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
| @@ -95,14 +101,16 @@ dependencies { | ||||
|     implementation fileTree(include: ['*.jar'], dir: 'libs') | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||
|     // Android Support | ||||
|     implementation "androidx.appcompat:appcompat:1.3.0-alpha02" | ||||
|     implementation 'com.google.android.material:material:1.3.0-beta01' | ||||
|     implementation 'androidx.recyclerview:recyclerview:1.2.0-beta01' | ||||
|     implementation "androidx.appcompat:appcompat:1.4.0-beta01" | ||||
|     implementation 'com.google.android.material:material:1.5.0-alpha04' | ||||
|     implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01' | ||||
|     implementation "androidx.legacy:legacy-support-v4:$android_version" | ||||
|     implementation 'androidx.vectordrawable:vectordrawable:1.2.0-alpha02' | ||||
|     implementation "androidx.browser:browser:1.3.0" | ||||
|     implementation "androidx.cardview:cardview:$android_version" | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.0-alpha2' | ||||
|     implementation "androidx.annotation:annotation:1.2.0" | ||||
|     implementation 'androidx.work:work-runtime-ktx:2.7.0' | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.1' | ||||
|     implementation 'org.jsoup:jsoup:1.13.1' | ||||
|  | ||||
|     //multidex | ||||
| @@ -113,23 +121,25 @@ dependencies { | ||||
|         transitive = true | ||||
|     } | ||||
|  | ||||
|     // Async | ||||
|     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2' | ||||
|  | ||||
|     // Retrofit + http logging + okhttp | ||||
|     implementation 'com.squareup.retrofit2:retrofit:2.3.0' | ||||
|     implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0' | ||||
|     implementation 'com.squareup.retrofit2:converter-gson:2.3.0' | ||||
|     implementation 'com.burgstaller:okhttp-digest:1.12' | ||||
|     implementation 'com.squareup.retrofit2:retrofit:2.9.0' | ||||
|     implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1' | ||||
|     implementation 'com.squareup.retrofit2:converter-gson:2.9.0' | ||||
|     implementation 'com.burgstaller:okhttp-digest:2.5' | ||||
|  | ||||
|     // Material-ish things | ||||
|     implementation 'com.ashokvarma.android:bottom-navigation-bar:2.1.0' | ||||
|     implementation 'com.github.jd-alexander:LikeButton:0.2.3' | ||||
|     implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' | ||||
|  | ||||
|     // glide | ||||
|     implementation 'com.github.bumptech.glide:glide:4.1.1' | ||||
|     kapt 'com.github.bumptech.glide:compiler:4.11.0' | ||||
|     implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.1' | ||||
|  | ||||
|     // Drawer | ||||
|     implementation 'co.zsmb:materialdrawer-kt:2.0.2' | ||||
|     implementation 'com.mikepenz:materialdrawer:8.4.4' | ||||
|  | ||||
|     // Themes | ||||
|     implementation 'com.52inc:scoops:1.0.0' | ||||
| @@ -142,13 +152,13 @@ dependencies { | ||||
|     //PhotoView | ||||
|     implementation 'com.github.chrisbanes:PhotoView:2.0.0' | ||||
|  | ||||
|     implementation 'androidx.core:core-ktx:1.5.0-alpha05' | ||||
|     implementation 'androidx.core:core-ktx:1.7.0-rc01' | ||||
|  | ||||
|     implementation "androidx.lifecycle:lifecycle-livedata:2.3.0-rc01" | ||||
|     implementation "androidx.lifecycle:lifecycle-common-java8:2.3.0-rc01" | ||||
|     implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-rc01" | ||||
|     implementation "androidx.lifecycle:lifecycle-common-java8:2.4.0-rc01" | ||||
|  | ||||
|     implementation "androidx.room:room-runtime:2.3.0-alpha04" | ||||
|     kapt "androidx.room:room-compiler:2.3.0-alpha04" | ||||
|     implementation "androidx.room:room-ktx:2.4.0-beta01" | ||||
|     kapt "androidx.room:room-compiler:2.4.0-beta01" | ||||
|  | ||||
|     implementation "android.arch.work:work-runtime-ktx:$work_version" | ||||
| } | ||||
|   | ||||
| @@ -15,7 +15,8 @@ | ||||
|         android:theme="@style/NoBar"> | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:theme="@style/SplashTheme"> | ||||
|             android:theme="@style/SplashTheme" | ||||
|             android:exported="true"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
| @@ -48,7 +49,8 @@ | ||||
|         </activity> | ||||
|         <activity | ||||
|             android:name=".AddSourceActivity" | ||||
|             android:parentActivityName=".SourcesActivity"> | ||||
|             android:parentActivityName=".SourcesActivity" | ||||
|             android:exported="true"> | ||||
|             <meta-data | ||||
|                 android:name="android.support.PARENT_ACTIVITY" | ||||
|                 android:value=".SourcesActivity" /> | ||||
|   | ||||
| @@ -206,42 +206,78 @@ class AddSourceActivity : AppCompatActivity() { | ||||
|  | ||||
|     private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) { | ||||
|  | ||||
|         val sourceDetailsAvailable = | ||||
|         val sourceDetailsUnavailable = | ||||
|             title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty() | ||||
|  | ||||
|         if (sourceDetailsAvailable) { | ||||
|             Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() | ||||
|         } else { | ||||
|             api.createSource( | ||||
|                 title, | ||||
|                 url, | ||||
|                 mSpoutsValue!!, | ||||
|                 tags.text.toString(), | ||||
|                 "" | ||||
|             ).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|                     if (response.body() != null && response.body()!!.isSuccess) { | ||||
|                         finish() | ||||
|                     } else { | ||||
|         when { | ||||
|             sourceDetailsUnavailable -> { | ||||
|                 Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show() | ||||
|             } | ||||
|             PreferenceManager.getDefaultSharedPreferences(this).getInt("apiVersionMajor", 0) > 1 -> { | ||||
|                 val tagList = tags.text.toString().split(",").map { it.trim() } | ||||
|                 api.createSourceApi2( | ||||
|                     title, | ||||
|                     url, | ||||
|                     mSpoutsValue!!, | ||||
|                     tagList, | ||||
|                     "" | ||||
|                 ).enqueue(object : Callback<SuccessResponse> { | ||||
|                     override fun onResponse( | ||||
|                         call: Call<SuccessResponse>, | ||||
|                         response: Response<SuccessResponse> | ||||
|                     ) { | ||||
|                         if (response.body() != null && response.body()!!.isSuccess) { | ||||
|                             finish() | ||||
|                         } else { | ||||
|                             Toast.makeText( | ||||
|                                 this@AddSourceActivity, | ||||
|                                 R.string.cant_create_source, | ||||
|                                 Toast.LENGTH_SHORT | ||||
|                             ).show() | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                         Toast.makeText( | ||||
|                             this@AddSourceActivity, | ||||
|                             R.string.cant_create_source, | ||||
|                             Toast.LENGTH_SHORT | ||||
|                         ).show() | ||||
|                     } | ||||
|                 } | ||||
|                 }) | ||||
|             } | ||||
|             else -> { | ||||
|                 api.createSource( | ||||
|                     title, | ||||
|                     url, | ||||
|                     mSpoutsValue!!, | ||||
|                     tags.text.toString(), | ||||
|                     "" | ||||
|                 ).enqueue(object : Callback<SuccessResponse> { | ||||
|                     override fun onResponse( | ||||
|                         call: Call<SuccessResponse>, | ||||
|                         response: Response<SuccessResponse> | ||||
|                     ) { | ||||
|                         if (response.body() != null && response.body()!!.isSuccess) { | ||||
|                             finish() | ||||
|                         } else { | ||||
|                             Toast.makeText( | ||||
|                                 this@AddSourceActivity, | ||||
|                                 R.string.cant_create_source, | ||||
|                                 Toast.LENGTH_SHORT | ||||
|                             ).show() | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                     Toast.makeText( | ||||
|                         this@AddSourceActivity, | ||||
|                         R.string.cant_create_source, | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                 } | ||||
|             }) | ||||
|                     override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                         Toast.makeText( | ||||
|                             this@AddSourceActivity, | ||||
|                             R.string.cant_create_source, | ||||
|                             Toast.LENGTH_SHORT | ||||
|                         ).show() | ||||
|                     } | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -60,23 +60,18 @@ class MyApp : MultiDexApplication() { | ||||
|  | ||||
|     private fun initDrawerImageLoader() { | ||||
|         DrawerImageLoader.init(object : AbstractDrawerImageLoader() { | ||||
|             override fun set( | ||||
|                 imageView: ImageView?, | ||||
|                 uri: Uri?, | ||||
|                 placeholder: Drawable?, | ||||
|                 tag: String? | ||||
|             ) { | ||||
|             override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) { | ||||
|                 Glide.with(imageView?.context) | ||||
|                     .loadMaybeBasicAuth(config, uri.toString()) | ||||
|                     .apply(RequestOptions.fitCenterTransform().placeholder(placeholder)) | ||||
|                     .into(imageView) | ||||
|             } | ||||
|  | ||||
|             override fun cancel(imageView: ImageView?) { | ||||
|             override fun cancel(imageView: ImageView) { | ||||
|                 Glide.with(imageView?.context).clear(imageView) | ||||
|             } | ||||
|  | ||||
|             override fun placeholder(ctx: Context?, tag: String?): Drawable { | ||||
|             override fun placeholder(ctx: Context, tag: String?): Drawable { | ||||
|                 return baseContext.resources.getDrawable(R.mipmap.ic_launcher) | ||||
|             } | ||||
|         }) | ||||
|   | ||||
| @@ -2,6 +2,9 @@ package apps.amine.bou.readerforselfoss | ||||
|  | ||||
| import android.content.Context | ||||
| import android.content.SharedPreferences | ||||
| import android.graphics.Color | ||||
| import android.graphics.PorterDuff | ||||
| import android.graphics.PorterDuffColorFilter | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.os.Build | ||||
| import android.os.Bundle | ||||
| @@ -14,17 +17,12 @@ import androidx.appcompat.app.AppCompatActivity | ||||
| import android.view.Menu | ||||
| import android.view.MenuItem | ||||
| import android.view.ViewGroup | ||||
| import android.widget.Toast | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.room.Room | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.databinding.ActivityImageBinding | ||||
| import apps.amine.bou.readerforselfoss.databinding.ActivityReaderBinding | ||||
| import apps.amine.bou.readerforselfoss.fragments.ArticleFragment | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4 | ||||
| @@ -32,16 +30,10 @@ import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.themes.Toppings | ||||
| import apps.amine.bou.readerforselfoss.transformers.DepthPageTransformer | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toEntity | ||||
| import apps.amine.bou.readerforselfoss.utils.succeeded | ||||
| import apps.amine.bou.readerforselfoss.utils.SharedItems | ||||
| import apps.amine.bou.readerforselfoss.utils.toggleStar | ||||
| import com.ftinc.scoop.Scoop | ||||
| import me.relex.circleindicator.CircleIndicator | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| class ReaderActivity : AppCompatActivity() { | ||||
|  | ||||
| @@ -62,8 +54,11 @@ class ReaderActivity : AppCompatActivity() { | ||||
|     val ALIGN_LEFT = 2 | ||||
|  | ||||
|     private fun showMenuItem(willAddToFavorite: Boolean) { | ||||
|         toolbarMenu.findItem(R.id.save).isVisible = willAddToFavorite | ||||
|         toolbarMenu.findItem(R.id.unsave).isVisible = !willAddToFavorite | ||||
|         if (willAddToFavorite) { | ||||
|             toolbarMenu.findItem(R.id.star).icon.colorFilter = PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP) | ||||
|         } else { | ||||
|             toolbarMenu.findItem(R.id.star).icon.colorFilter = PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun canFavorite() { | ||||
| @@ -146,42 +141,16 @@ class ReaderActivity : AppCompatActivity() { | ||||
|                     } else { | ||||
|                         canFavorite() | ||||
|                     } | ||||
|                     readItem(allItems[binding.pager.currentItem]) | ||||
|                     readItem(allItems[position]) | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     fun readItem(item: Item) { | ||||
|     private fun readItem(item: Item) { | ||||
|         if (markOnScroll) { | ||||
|             thread { | ||||
|                 db.itemsDao().delete(item.toEntity()) | ||||
|                 SharedItems.readItem(applicationContext, api, db, item) | ||||
|             } | ||||
|             if (this@ReaderActivity.isNetworkAccessible(this@ReaderActivity.findViewById(R.id.reader_activity_view))) { | ||||
|                 api.markItem(item.id).enqueue( | ||||
|                     object : Callback<SuccessResponse> { | ||||
|                         override fun onResponse( | ||||
|                             call: Call<SuccessResponse>, | ||||
|                             response: Response<SuccessResponse> | ||||
|                         ) { | ||||
|                         } | ||||
|  | ||||
|                         override fun onFailure( | ||||
|                             call: Call<SuccessResponse>, | ||||
|                             t: Throwable | ||||
|                         ) { | ||||
|                             thread { | ||||
|                                 db.itemsDao().insertAllItems(item.toEntity()) | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 ) | ||||
|             } else { | ||||
|                 thread { | ||||
|                     db.actionsDao().insertAllActions(ActionEntity(item.id, true, false, false, false)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun notifyAdapter() { | ||||
| @@ -266,62 +235,23 @@ class ReaderActivity : AppCompatActivity() { | ||||
|                 onBackPressed() | ||||
|                 return true | ||||
|             } | ||||
|             R.id.save -> { | ||||
|                 if (this@ReaderActivity.isNetworkAccessible(null)) { | ||||
|                     api.starrItem(allItems[binding.pager.currentItem].id) | ||||
|                         .enqueue(object : Callback<SuccessResponse> { | ||||
|                             override fun onResponse( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 response: Response<SuccessResponse> | ||||
|                             ) { | ||||
|                                 afterSave() | ||||
|                             } | ||||
|  | ||||
|                             override fun onFailure( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 t: Throwable | ||||
|                             ) { | ||||
|                                 Toast.makeText( | ||||
|                                     baseContext, | ||||
|                                     R.string.cant_mark_favortie, | ||||
|                                     Toast.LENGTH_SHORT | ||||
|                                 ).show() | ||||
|                             } | ||||
|                         }) | ||||
|             R.id.star -> { | ||||
|                 if (allItems[binding.pager.currentItem].starred) { | ||||
|                     SharedItems.unstarItem( | ||||
|                         this@ReaderActivity, | ||||
|                         api, | ||||
|                         db, | ||||
|                         allItems[binding.pager.currentItem] | ||||
|                     ) | ||||
|                     afterUnsave() | ||||
|                 } else { | ||||
|                     thread { | ||||
|                         db.actionsDao().insertAllActions(ActionEntity(allItems[binding.pager.currentItem].id, false, false, true, false)) | ||||
|                         afterSave() | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             R.id.unsave -> { | ||||
|                 if (this@ReaderActivity.isNetworkAccessible(null)) { | ||||
|                     api.unstarrItem(allItems[binding.pager.currentItem].id) | ||||
|                         .enqueue(object : Callback<SuccessResponse> { | ||||
|                             override fun onResponse( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 response: Response<SuccessResponse> | ||||
|                             ) { | ||||
|                                 afterUnsave() | ||||
|                             } | ||||
|  | ||||
|                             override fun onFailure( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 t: Throwable | ||||
|                             ) { | ||||
|                                 Toast.makeText( | ||||
|                                     baseContext, | ||||
|                                     R.string.cant_unmark_favortie, | ||||
|                                     Toast.LENGTH_SHORT | ||||
|                                 ).show() | ||||
|                             } | ||||
|                         }) | ||||
|                 } else { | ||||
|                     thread { | ||||
|                         db.actionsDao().insertAllActions(ActionEntity(allItems[binding.pager.currentItem].id, false, false, false, true)) | ||||
|                         afterUnsave() | ||||
|                     } | ||||
|                     SharedItems.starItem( | ||||
|                         this@ReaderActivity, | ||||
|                         api, | ||||
|                         db, | ||||
|                         allItems[binding.pager.currentItem] | ||||
|                     ) | ||||
|                     afterSave() | ||||
|                 } | ||||
|             } | ||||
|             R.id.align_left -> { | ||||
|   | ||||
| @@ -2,29 +2,26 @@ package apps.amine.bou.readerforselfoss.adapters | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import androidx.cardview.widget.CardView | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.ImageView.ScaleType | ||||
| import android.widget.Toast | ||||
| import androidx.core.content.ContextCompat | ||||
| import apps.amine.bou.readerforselfoss.R | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.databinding.CardItemBinding | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.LinkOnTouchListener | ||||
| import apps.amine.bou.readerforselfoss.utils.SharedItems | ||||
| import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent | ||||
| import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAvailable | ||||
| import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask | ||||
| import apps.amine.bou.readerforselfoss.utils.openItemUrl | ||||
| import apps.amine.bou.readerforselfoss.utils.shareLink | ||||
| @@ -33,12 +30,6 @@ import apps.amine.bou.readerforselfoss.utils.toTextDrawableString | ||||
| import com.amulyakhare.textdrawable.TextDrawable | ||||
| import com.amulyakhare.textdrawable.util.ColorGenerator | ||||
| import com.bumptech.glide.Glide | ||||
| import com.like.LikeButton | ||||
| import com.like.OnLikeListener | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| class ItemCardAdapter( | ||||
|     override val app: Activity, | ||||
| @@ -68,13 +59,13 @@ class ItemCardAdapter( | ||||
|         with(holder) { | ||||
|             val itm = items[position] | ||||
|  | ||||
|  | ||||
|             binding.favButton.isLiked = itm.starred | ||||
|             binding.favButton.isSelected = itm.starred | ||||
|             binding.title.text = itm.getTitleDecoded() | ||||
|             binding.title.setTextColor(ContextCompat.getColor( | ||||
|                     c, | ||||
|                     appColors.textColor | ||||
|             )) | ||||
|  | ||||
|             binding.title.setOnTouchListener(LinkOnTouchListener()) | ||||
|  | ||||
|             binding.title.setLinkTextColor(appColors.colorAccent) | ||||
| @@ -112,8 +103,6 @@ class ItemCardAdapter( | ||||
|             } else { | ||||
|                 c.circularBitmapDrawable(config, itm.getIcon(c), binding.sourceImage) | ||||
|             } | ||||
|  | ||||
|             binding.favButton.isLiked = itm.starred | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -130,75 +119,30 @@ class ItemCardAdapter( | ||||
|  | ||||
|         private fun handleClickListeners() { | ||||
|  | ||||
|             binding.favButton.setOnLikeListener(object : OnLikeListener { | ||||
|                 override fun liked(likeButton: LikeButton) { | ||||
|                     val (id) = items[bindingAdapterPosition] | ||||
|                     if (c.isNetworkAccessible(null)) { | ||||
|                         api.starrItem(id).enqueue(object : Callback<SuccessResponse> { | ||||
|                             override fun onResponse( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 response: Response<SuccessResponse> | ||||
|                             ) { | ||||
|                             } | ||||
|  | ||||
|                             override fun onFailure( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 t: Throwable | ||||
|                             ) { | ||||
|                                 binding.favButton.isLiked = false | ||||
|                                 Toast.makeText( | ||||
|                                     c, | ||||
|                                     R.string.cant_mark_favortie, | ||||
|                                     Toast.LENGTH_SHORT | ||||
|                                 ).show() | ||||
|                             } | ||||
|                         }) | ||||
|                     } else { | ||||
|                         thread { | ||||
|                             db.actionsDao().insertAllActions(ActionEntity(id, false, false, true, false)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 override fun unLiked(likeButton: LikeButton) { | ||||
|                     val (id) = items[bindingAdapterPosition] | ||||
|                     if (c.isNetworkAccessible(null)) { | ||||
|                         api.unstarrItem(id).enqueue(object : Callback<SuccessResponse> { | ||||
|                             override fun onResponse( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 response: Response<SuccessResponse> | ||||
|                             ) { | ||||
|                             } | ||||
|  | ||||
|                             override fun onFailure( | ||||
|                                 call: Call<SuccessResponse>, | ||||
|                                 t: Throwable | ||||
|                             ) { | ||||
|                                 binding.favButton.isLiked = true | ||||
|                                 Toast.makeText( | ||||
|                                     c, | ||||
|                                     R.string.cant_unmark_favortie, | ||||
|                                     Toast.LENGTH_SHORT | ||||
|                                 ).show() | ||||
|                             } | ||||
|                         }) | ||||
|                     } else { | ||||
|                         thread { | ||||
|                             db.actionsDao().insertAllActions(ActionEntity(id, false, false, false, true)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|  | ||||
|             binding.shareBtn.setOnClickListener { | ||||
|             binding.favButton.setOnClickListener { | ||||
|                 val item = items[bindingAdapterPosition] | ||||
|                 c.shareLink(item.getLinkDecoded(), item.getTitleDecoded()) | ||||
|                 if (isNetworkAvailable(c)) { | ||||
|                     if (item.starred) { | ||||
|                         SharedItems.unstarItem(c, api, db, item) | ||||
|                         item.starred = false | ||||
|                         binding.favButton.isSelected = false | ||||
|                     } else { | ||||
|                         SharedItems.starItem(c, api, db, item) | ||||
|                         item.starred = true | ||||
|                         binding.favButton.isSelected = true | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             binding.browserBtn.setOnClickListener { | ||||
|                 c.openInBrowserAsNewTask(items[bindingAdapterPosition]) | ||||
|                 binding.shareBtn.setOnClickListener { | ||||
|                     val item = items[bindingAdapterPosition] | ||||
|                     c.shareLink(item.getLinkDecoded(), item.getTitleDecoded()) | ||||
|                 } | ||||
|  | ||||
|                 binding.browserBtn.setOnClickListener { | ||||
|                     c.openInBrowserAsNewTask(items[bindingAdapterPosition]) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun handleCustomTabActions() { | ||||
|             val customTabsIntent = c.buildCustomTabsIntent() | ||||
|   | ||||
| @@ -2,22 +2,12 @@ package apps.amine.bou.readerforselfoss.adapters | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import androidx.constraintlayout.widget.ConstraintLayout | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import android.text.Spannable | ||||
| import android.text.style.ClickableSpan | ||||
| import android.util.TypedValue | ||||
| import android.view.LayoutInflater | ||||
| import android.view.MotionEvent | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.TextView | ||||
| import android.widget.Toast | ||||
| import androidx.core.content.ContextCompat | ||||
| import apps.amine.bou.readerforselfoss.R | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.databinding.ListItemBinding | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| @@ -27,19 +17,11 @@ import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent | ||||
| import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.bitmapCenterCrop | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.circularBitmapDrawable | ||||
| import apps.amine.bou.readerforselfoss.utils.openInBrowserAsNewTask | ||||
| import apps.amine.bou.readerforselfoss.utils.openItemUrl | ||||
| import apps.amine.bou.readerforselfoss.utils.shareLink | ||||
| import apps.amine.bou.readerforselfoss.utils.sourceAndDateText | ||||
| import apps.amine.bou.readerforselfoss.utils.toTextDrawableString | ||||
| import com.amulyakhare.textdrawable.TextDrawable | ||||
| import com.amulyakhare.textdrawable.util.ColorGenerator | ||||
| import com.like.LikeButton | ||||
| import com.like.OnLikeListener | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| import java.util.* | ||||
| import kotlin.collections.ArrayList | ||||
|  | ||||
| class ItemListAdapter( | ||||
|   | ||||
| @@ -2,25 +2,16 @@ package apps.amine.bou.readerforselfoss.adapters | ||||
|  | ||||
| import android.app.Activity | ||||
| import android.graphics.Color | ||||
| import com.google.android.material.snackbar.Snackbar | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import android.widget.TextView | ||||
| import android.widget.Toast | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import apps.amine.bou.readerforselfoss.R | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toEntity | ||||
| import apps.amine.bou.readerforselfoss.utils.succeeded | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| import kotlin.concurrent.thread | ||||
| import apps.amine.bou.readerforselfoss.utils.SharedItems | ||||
| import com.google.android.material.snackbar.Snackbar | ||||
|  | ||||
| abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() { | ||||
|     abstract var items: ArrayList<Item> | ||||
| @@ -32,8 +23,8 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|     abstract val config: Config | ||||
|     abstract val updateItems: (ArrayList<Item>) -> Unit | ||||
|  | ||||
|     fun updateAllItems(newItems: ArrayList<Item>) { | ||||
|         items = newItems | ||||
|     fun updateAllItems() { | ||||
|         items = SharedItems.focusedItems | ||||
|         notifyDataSetChanged() | ||||
|         updateItems(items) | ||||
|     } | ||||
| @@ -46,34 +37,11 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                 Snackbar.LENGTH_LONG | ||||
|             ) | ||||
|             .setAction(R.string.undo_string) { | ||||
|                 items.add(position, i) | ||||
|                 thread { | ||||
|                     db.itemsDao().insertAllItems(i.toEntity()) | ||||
|                 } | ||||
|                 notifyItemInserted(position) | ||||
|                 updateItems(items) | ||||
|  | ||||
|                 if (app.isNetworkAccessible(null)) { | ||||
|                     api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                         override fun onResponse( | ||||
|                             call: Call<SuccessResponse>, | ||||
|                             response: Response<SuccessResponse> | ||||
|                         ) { | ||||
|                         } | ||||
|  | ||||
|                         override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                             items.remove(i) | ||||
|                             thread { | ||||
|                                 db.itemsDao().delete(i.toEntity()) | ||||
|                             } | ||||
|                             notifyItemRemoved(position) | ||||
|                             updateItems(items) | ||||
|                         } | ||||
|                     }) | ||||
|                 SharedItems.unreadItem(app, api, db, i) | ||||
|                 if (SharedItems.displayedItems == "unread") { | ||||
|                     addItemAtIndex(i, position) | ||||
|                 } else { | ||||
|                     thread { | ||||
|                         db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false)) | ||||
|                     } | ||||
|                     notifyItemChanged(position) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -83,7 +51,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|         s.show() | ||||
|     } | ||||
|  | ||||
|     private fun markSnackbar(i: Item, position: Int) { | ||||
|     private fun markSnackbar(position: Int) { | ||||
|         val s = Snackbar | ||||
|             .make( | ||||
|                 app.findViewById(R.id.coordLayout), | ||||
| @@ -91,34 +59,13 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                 Snackbar.LENGTH_LONG | ||||
|             ) | ||||
|             .setAction(R.string.undo_string) { | ||||
|                 items.add(position, i) | ||||
|                 thread { | ||||
|                     db.itemsDao().delete(i.toEntity()) | ||||
|                 } | ||||
|                 notifyItemInserted(position) | ||||
|                 updateItems(items) | ||||
|  | ||||
|                 if (app.isNetworkAccessible(null)) { | ||||
|                     api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                         override fun onResponse( | ||||
|                             call: Call<SuccessResponse>, | ||||
|                             response: Response<SuccessResponse> | ||||
|                         ) { | ||||
|                         } | ||||
|  | ||||
|                         override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                             items.remove(i) | ||||
|                             thread { | ||||
|                                 db.itemsDao().insertAllItems(i.toEntity()) | ||||
|                             } | ||||
|                             notifyItemRemoved(position) | ||||
|                             updateItems(items) | ||||
|                         } | ||||
|                     }) | ||||
|                 SharedItems.readItem(app, api, db, items[position]) | ||||
|                 items = SharedItems.focusedItems | ||||
|                 if (SharedItems.displayedItems == "unread") { | ||||
|                     notifyItemRemoved(position) | ||||
|                     updateItems(items) | ||||
|                 } else { | ||||
|                     thread { | ||||
|                         db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) | ||||
|                     } | ||||
|                     notifyItemChanged(position) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -129,99 +76,30 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|     } | ||||
|  | ||||
|     fun handleItemAtIndex(position: Int) { | ||||
|         if (unreadItemStatusAtIndex(position)) { | ||||
|         if (SharedItems.unreadItemStatusAtIndex(position)) { | ||||
|             readItemAtIndex(position) | ||||
|         } else { | ||||
|             unreadItemAtIndex(position) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun unreadItemStatusAtIndex(position: Int): Boolean { | ||||
|         return items[position].unread | ||||
|     } | ||||
|  | ||||
|     private fun readItemAtIndex(position: Int) { | ||||
|         val i = items[position] | ||||
|         items.remove(i) | ||||
|         notifyItemRemoved(position) | ||||
|         updateItems(items) | ||||
|  | ||||
|         thread { | ||||
|             db.itemsDao().delete(i.toEntity()) | ||||
|         } | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|  | ||||
|                     unmarkSnackbar(i, position) | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                     Toast.makeText( | ||||
|                         app, | ||||
|                         app.getString(R.string.cant_mark_read), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                     items.add(position, i) | ||||
|                     notifyItemInserted(position) | ||||
|                     updateItems(items) | ||||
|  | ||||
|                     thread { | ||||
|                         db.itemsDao().insertAllItems(i.toEntity()) | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         SharedItems.readItem(app, api, db, i) | ||||
|         if (SharedItems.displayedItems == "unread") { | ||||
|             items.remove(i) | ||||
|             notifyItemRemoved(position) | ||||
|             updateItems(items) | ||||
|         } else { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) | ||||
|             } | ||||
|             notifyItemChanged(position) | ||||
|         } | ||||
|         unmarkSnackbar(i, position) | ||||
|     } | ||||
|  | ||||
|     private fun unreadItemAtIndex(position: Int) { | ||||
|         val i = items[position] | ||||
|         items.remove(i) | ||||
|         notifyItemRemoved(position) | ||||
|         updateItems(items) | ||||
|  | ||||
|         thread { | ||||
|             db.itemsDao().insertAllItems(i.toEntity()) | ||||
|         } | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|  | ||||
|                     markSnackbar(i, position) | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                     Toast.makeText( | ||||
|                         app, | ||||
|                         app.getString(R.string.cant_mark_unread), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                     items.add(i) | ||||
|                     notifyItemInserted(position) | ||||
|                     updateItems(items) | ||||
|  | ||||
|                     thread { | ||||
|                         db.itemsDao().delete(i.toEntity()) | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|         } else { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false)) | ||||
|             } | ||||
|         } | ||||
|         SharedItems.unreadItem(app, api, db, items[position]) | ||||
|         notifyItemChanged(position) | ||||
|         markSnackbar(position) | ||||
|     } | ||||
|  | ||||
|     fun addItemAtIndex(item: Item, position: Int) { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package apps.amine.bou.readerforselfoss.api.selfoss | ||||
| import android.app.Activity | ||||
| import android.content.Context | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.SharedItems | ||||
| import apps.amine.bou.readerforselfoss.utils.getUnsafeHttpClient | ||||
| import com.burgstaller.okhttp.AuthenticationCacheInterceptor | ||||
| import com.burgstaller.okhttp.CachingAuthenticatorDecorator | ||||
| @@ -13,6 +14,8 @@ import com.burgstaller.okhttp.digest.Credentials | ||||
| import com.burgstaller.okhttp.digest.DigestAuthenticator | ||||
| import com.google.gson.GsonBuilder | ||||
| import okhttp3.* | ||||
| import okhttp3.MediaType.Companion.toMediaTypeOrNull | ||||
| import okhttp3.ResponseBody.Companion.toResponseBody | ||||
| import okhttp3.logging.HttpLoggingInterceptor | ||||
| import retrofit2.Call | ||||
| import retrofit2.Retrofit | ||||
| @@ -67,7 +70,7 @@ class SelfossApi( | ||||
|                     val request: Request = chain.request() | ||||
|                     val response: Response = chain.proceed(request) | ||||
|  | ||||
|                     if (response.code() == 408) { | ||||
|                     if (response.code == 408) { | ||||
|                         return response | ||||
|                     } | ||||
|                     return response | ||||
| @@ -102,7 +105,7 @@ class SelfossApi( | ||||
|         httpClient | ||||
|                 .addInterceptor { chain -> | ||||
|                     val res = chain.proceed(chain.request()) | ||||
|                     if (res.code() == timeoutCode) { | ||||
|                     if (res.code == timeoutCode) { | ||||
|                         throw SocketTimeoutException("timeout") | ||||
|                     } | ||||
|                     res | ||||
| @@ -116,7 +119,7 @@ class SelfossApi( | ||||
|                         Response.Builder() | ||||
|                                 .code(timeoutCode) | ||||
|                                 .protocol(Protocol.HTTP_2) | ||||
|                                 .body(ResponseBody.create(MediaType.parse("text/plain"), "")) | ||||
|                                 .body("".toResponseBody("text/plain".toMediaTypeOrNull())) | ||||
|                                 .message("") | ||||
|                                 .request(request) | ||||
|                                 .build() | ||||
| @@ -142,54 +145,50 @@ class SelfossApi( | ||||
|     fun login(): Call<SuccessResponse> = | ||||
|         service.loginToSelfoss(config.userLogin, config.userPassword) | ||||
|  | ||||
|     fun readItems( | ||||
|         tag: String?, | ||||
|         sourceId: Long?, | ||||
|         search: String?, | ||||
|     suspend fun readItems( | ||||
|         itemsNumber: Int, | ||||
|         offset: Int | ||||
|     ): Call<List<Item>> = | ||||
|         getItems("read", tag, sourceId, search, itemsNumber, offset) | ||||
|     ): retrofit2.Response<List<Item>> = | ||||
|         getItems("read", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset) | ||||
|  | ||||
|     fun newItems( | ||||
|         tag: String?, | ||||
|         sourceId: Long?, | ||||
|         search: String?, | ||||
|     suspend fun newItems( | ||||
|         itemsNumber: Int, | ||||
|         offset: Int | ||||
|     ): Call<List<Item>> = | ||||
|         getItems("unread", tag, sourceId, search, itemsNumber, offset) | ||||
|     ): retrofit2.Response<List<Item>> = | ||||
|         getItems("unread", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset) | ||||
|  | ||||
|     fun starredItems( | ||||
|         tag: String?, | ||||
|         sourceId: Long?, | ||||
|         search: String?, | ||||
|     suspend fun starredItems( | ||||
|         itemsNumber: Int, | ||||
|         offset: Int | ||||
|     ): Call<List<Item>> = | ||||
|         getItems("starred", tag, sourceId, search, itemsNumber, offset) | ||||
|     ): retrofit2.Response<List<Item>> = | ||||
|         getItems("starred", SharedItems.tagFilter, SharedItems.sourceIDFilter, SharedItems.searchFilter, itemsNumber, offset) | ||||
|  | ||||
|     fun allItems(): Call<List<Item>> = | ||||
|         service.allItems(userName, password) | ||||
|  | ||||
|     fun allNewItems(): Call<List<Item>> = | ||||
|     suspend fun allNewItems(): retrofit2.Response<List<Item>> = | ||||
|             getItems("unread", null, null, null, 200, 0) | ||||
|  | ||||
|     fun allReadItems(): Call<List<Item>> = | ||||
|     suspend fun allReadItems(): retrofit2.Response<List<Item>> = | ||||
|             getItems("read", null, null, null, 200, 0) | ||||
|  | ||||
|     fun allStarredItems(): Call<List<Item>> = | ||||
|             getItems("read", null, null, null, 200, 0) | ||||
|     suspend fun allStarredItems(): retrofit2.Response<List<Item>> = | ||||
|         getItems("read", null, null, null, 200, 0) | ||||
|  | ||||
|     private fun getItems( | ||||
|     private suspend fun getItems( | ||||
|         type: String, | ||||
|         tag: String?, | ||||
|         sourceId: Long?, | ||||
|         search: String?, | ||||
|         items: Int, | ||||
|         offset: Int | ||||
|     ): Call<List<Item>> = | ||||
|         service.getItems(type, tag, sourceId, search, userName, password, items, offset) | ||||
|     ): retrofit2.Response<List<Item>> = | ||||
|         service.getItems(type, tag, sourceId, search, null, userName, password, items, offset) | ||||
|  | ||||
|     suspend fun updateItems( | ||||
|         updatedSince: String | ||||
|     ): retrofit2.Response<List<Item>> = | ||||
|         service.getItems("read", null, null, null, updatedSince, userName, password, 200, 0) | ||||
|  | ||||
|     fun markItem(itemId: String): Call<SuccessResponse> = | ||||
|         service.markAsRead(itemId, userName, password) | ||||
| @@ -197,7 +196,7 @@ class SelfossApi( | ||||
|     fun unmarkItem(itemId: String): Call<SuccessResponse> = | ||||
|         service.unmarkAsRead(itemId, userName, password) | ||||
|  | ||||
|     fun readAll(ids: List<String>): Call<SuccessResponse> = | ||||
|     suspend fun readAll(ids: List<String>): SuccessResponse = | ||||
|         service.markAllAsRead(ids, userName, password) | ||||
|  | ||||
|     fun starrItem(itemId: String): Call<SuccessResponse> = | ||||
| @@ -206,8 +205,7 @@ class SelfossApi( | ||||
|     fun unstarrItem(itemId: String): Call<SuccessResponse> = | ||||
|         service.unstarr(itemId, userName, password) | ||||
|  | ||||
|     val stats: Call<Stats> | ||||
|         get() = service.stats(userName, password) | ||||
|     suspend fun stats(): retrofit2.Response<Stats> = service.stats(userName, password) | ||||
|  | ||||
|     val tags: Call<List<Tag>> | ||||
|         get() = service.tags(userName, password) | ||||
| @@ -235,4 +233,13 @@ class SelfossApi( | ||||
|         filter: String | ||||
|     ): Call<SuccessResponse> = | ||||
|         service.createSource(title, url, spout, tags, filter, userName, password) | ||||
|  | ||||
|     fun createSourceApi2( | ||||
|         title: String, | ||||
|         url: String, | ||||
|         spout: String, | ||||
|         tags: List<String>, | ||||
|         filter: String | ||||
|     ): Call<SuccessResponse> = | ||||
|         service.createSourceApi2(title, url, spout, tags, filter, userName, password) | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,134 @@ | ||||
| package apps.amine.bou.readerforselfoss.api.selfoss | ||||
|  | ||||
| import android.content.Context | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.utils.SharedItems | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAvailable | ||||
| import kotlinx.coroutines.* | ||||
| import retrofit2.Response | ||||
|  | ||||
| suspend fun getAndStoreAllItems(context: Context, api: SelfossApi, db: AppDatabase) = withContext(Dispatchers.IO) { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         launch { | ||||
|             try { | ||||
|                 enqueueArticles(api.allNewItems(), db, true) | ||||
|             } catch (e: Throwable) {} | ||||
|         } | ||||
|         launch { | ||||
|             try { | ||||
|                 enqueueArticles(api.allReadItems(), db, false) | ||||
|             } catch (e: Throwable) {} | ||||
|         } | ||||
|         launch { | ||||
|             try { | ||||
|                 enqueueArticles(api.allStarredItems(), db, false) | ||||
|             } catch (e: Throwable) {} | ||||
|         } | ||||
|     } else { | ||||
|         launch { SharedItems.updateDatabase(db) } | ||||
|     } | ||||
| } | ||||
|  | ||||
| suspend fun updateItems(context: Context, api: SelfossApi, db: AppDatabase) = coroutineScope { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         launch { | ||||
|             try { | ||||
|                 enqueueArticles(api.updateItems(SharedItems.items[0].datetime), db, true) | ||||
|             } catch (e: Throwable) {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| suspend fun refreshFocusedItems(context: Context, api: SelfossApi, db: AppDatabase) = withContext(Dispatchers.IO) { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         val response = when (SharedItems.displayedItems) { | ||||
|             "read" -> api.readItems(200, 0) | ||||
|             "unread" -> api.newItems(200, 0) | ||||
|             "starred" -> api.starredItems(200, 0) | ||||
|             else -> api.readItems(200, 0) | ||||
|         } | ||||
|  | ||||
|         if (response.isSuccessful) { | ||||
|             SharedItems.refreshFocusedItems(response.body() as ArrayList<Item>) | ||||
|             SharedItems.updateDatabase(db) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| suspend fun getReadItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|             try { | ||||
|                 enqueueArticles(api.readItems( 200, offset), db, false) | ||||
|                 SharedItems.fetchedAll = true | ||||
|                 SharedItems.updateDatabase(db) | ||||
|             } catch (e: Throwable) {} | ||||
|     } | ||||
| } | ||||
|  | ||||
| suspend fun getUnreadItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         try { | ||||
|             if (!SharedItems.fetchedUnread) { | ||||
|                 SharedItems.clearDBItems(db) | ||||
|             } | ||||
|             enqueueArticles(api.newItems(200, offset), db, false) | ||||
|             SharedItems.fetchedUnread = true | ||||
|         } catch (e: Throwable) {} | ||||
|     } | ||||
|     SharedItems.updateDatabase(db) | ||||
| } | ||||
|  | ||||
| suspend fun getStarredItems(context: Context, api: SelfossApi, db: AppDatabase, offset: Int) = withContext(Dispatchers.IO) { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         try { | ||||
|             enqueueArticles(api.starredItems(200, offset), db, false) | ||||
|             SharedItems.fetchedStarred = true | ||||
|             SharedItems.updateDatabase(db) | ||||
|         } catch (e: Throwable) { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| suspend fun readAll(context: Context, api: SelfossApi, db: AppDatabase): Boolean { | ||||
|     var success = false | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         try { | ||||
|             val ids = SharedItems.focusedItems.map { it.id } | ||||
|             if (ids.isNotEmpty()) { | ||||
|                 val result = api.readAll(ids) | ||||
|                 SharedItems.readItems(db, ids) | ||||
|                 success = result.isSuccess | ||||
|             } | ||||
|         } catch (e: Throwable) {} | ||||
|     } | ||||
|     return success | ||||
| } | ||||
|  | ||||
| suspend fun reloadBadges(context: Context, api: SelfossApi) = withContext(Dispatchers.IO) { | ||||
|     if (isNetworkAvailable(context)) { | ||||
|         try { | ||||
|             val response = api.stats() | ||||
|  | ||||
|             if (response.isSuccessful) { | ||||
|                 val badges = response.body() | ||||
|                 SharedItems.badgeUnread = badges!!.unread | ||||
|                 SharedItems.badgeAll = badges.total | ||||
|                 SharedItems.badgeStarred = badges.starred | ||||
|             } | ||||
|         } catch (e: Throwable) {} | ||||
|     } else { | ||||
|         SharedItems.computeBadges() | ||||
|     } | ||||
| } | ||||
|  | ||||
| private fun enqueueArticles(response: Response<List<Item>>, db: AppDatabase, clearDatabase: Boolean) { | ||||
|         if (response.isSuccessful) { | ||||
|             if (clearDatabase) { | ||||
|                 CoroutineScope(Dispatchers.IO).launch { | ||||
|                     SharedItems.clearDBItems(db) | ||||
|                 } | ||||
|             } | ||||
|             val allItems = response.body() as ArrayList<Item> | ||||
|             SharedItems.appendNewItems(allItems) | ||||
|         } | ||||
| } | ||||
| @@ -30,7 +30,11 @@ data class Tag( | ||||
|     @SerializedName("tag") val tag: String, | ||||
|     @SerializedName("color") val color: String, | ||||
|     @SerializedName("unread") val unread: Int | ||||
| ) | ||||
| ) { | ||||
|     fun getTitleDecoded(): String { | ||||
|         return Html.fromHtml(tag).toString() | ||||
|     } | ||||
| } | ||||
|  | ||||
| class SuccessResponse(@SerializedName("success") val success: Boolean) { | ||||
|     val isSuccess: Boolean | ||||
| @@ -88,7 +92,7 @@ data class Item( | ||||
|     @SerializedName("datetime") val datetime: String, | ||||
|     @SerializedName("title") val title: String, | ||||
|     @SerializedName("content") val content: String, | ||||
|     @SerializedName("unread") val unread: Boolean, | ||||
|     @SerializedName("unread") var unread: Boolean, | ||||
|     @SerializedName("starred") var starred: Boolean, | ||||
|     @SerializedName("thumbnail") val thumbnail: String?, | ||||
|     @SerializedName("icon") val icon: String?, | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package apps.amine.bou.readerforselfoss.api.selfoss | ||||
|  | ||||
| import retrofit2.Call | ||||
| import retrofit2.Response | ||||
| import retrofit2.http.DELETE | ||||
| import retrofit2.http.Field | ||||
| import retrofit2.http.FormUrlEncoded | ||||
| @@ -16,16 +17,17 @@ internal interface SelfossService { | ||||
|     fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call<SuccessResponse> | ||||
|  | ||||
|     @GET("items") | ||||
|     fun getItems( | ||||
|     suspend fun getItems( | ||||
|         @Query("type") type: String, | ||||
|         @Query("tag") tag: String?, | ||||
|         @Query("source") source: Long?, | ||||
|         @Query("search") search: String?, | ||||
|         @Query("updatedsince") updatedSince: String?, | ||||
|         @Query("username") username: String, | ||||
|         @Query("password") password: String, | ||||
|         @Query("items") items: Int, | ||||
|         @Query("offset") offset: Int | ||||
|     ): Call<List<Item>> | ||||
|     ): Response<List<Item>> | ||||
|  | ||||
|     @GET("items") | ||||
|     fun allItems( | ||||
| @@ -51,11 +53,11 @@ internal interface SelfossService { | ||||
|  | ||||
|     @FormUrlEncoded | ||||
|     @POST("mark") | ||||
|     fun markAllAsRead( | ||||
|     suspend fun markAllAsRead( | ||||
|         @Field("ids[]") ids: List<String>, | ||||
|         @Query("username") username: String, | ||||
|         @Query("password") password: String | ||||
|     ): Call<SuccessResponse> | ||||
|     ): SuccessResponse | ||||
|  | ||||
|     @Headers("Content-Type: application/x-www-form-urlencoded") | ||||
|     @POST("starr/{id}") | ||||
| @@ -74,10 +76,10 @@ internal interface SelfossService { | ||||
|     ): Call<SuccessResponse> | ||||
|  | ||||
|     @GET("stats") | ||||
|     fun stats( | ||||
|     suspend fun stats( | ||||
|         @Query("username") username: String, | ||||
|         @Query("password") password: String | ||||
|     ): Call<Stats> | ||||
|     ): Response<Stats> | ||||
|  | ||||
|     @GET("tags") | ||||
|     fun tags( | ||||
| @@ -124,4 +126,16 @@ internal interface SelfossService { | ||||
|         @Query("username") username: String, | ||||
|         @Query("password") password: String | ||||
|     ): Call<SuccessResponse> | ||||
|  | ||||
|     @FormUrlEncoded | ||||
|     @POST("source") | ||||
|     fun createSourceApi2( | ||||
|         @Field("title") title: String, | ||||
|         @Field("url") url: String, | ||||
|         @Field("spout") spout: String, | ||||
|         @Field("tags[]") tags: List<String>, | ||||
|         @Field("filter") filter: String, | ||||
|         @Query("username") username: String, | ||||
|         @Query("password") password: String | ||||
|     ): Call<SuccessResponse> | ||||
| } | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import android.app.NotificationManager | ||||
| import android.app.PendingIntent | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.os.Build | ||||
| import android.preference.PreferenceManager | ||||
| import androidx.core.app.NotificationCompat | ||||
| import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT | ||||
| @@ -13,16 +14,19 @@ import androidx.work.Worker | ||||
| import androidx.work.WorkerParameters | ||||
| import apps.amine.bou.readerforselfoss.MainActivity | ||||
| import apps.amine.bou.readerforselfoss.R | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.getAndStoreAllItems | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4 | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toEntity | ||||
| import apps.amine.bou.readerforselfoss.utils.SharedItems | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAvailable | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| @@ -33,123 +37,103 @@ import kotlin.concurrent.thread | ||||
| class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params) { | ||||
|     lateinit var db: AppDatabase | ||||
|  | ||||
|     override fun doWork(): Result { | ||||
|         if (context.isNetworkAccessible(null)) { | ||||
| override fun doWork(): Result { | ||||
|     val settings = | ||||
|         this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
|     val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context) | ||||
|     val periodicRefresh = sharedPref.getBoolean("periodic_refresh", false) | ||||
|     if (periodicRefresh) { | ||||
|         val api = SelfossApi( | ||||
|             this.context, | ||||
|             null, | ||||
|             settings.getBoolean("isSelfSignedCert", false), | ||||
|             sharedPref.getString("api_timeout", "-1")!!.toLong() | ||||
|         ) | ||||
|  | ||||
|             val notificationManager = | ||||
|                 applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | ||||
|         if (isNetworkAvailable(context)) { | ||||
|  | ||||
|             val notification = NotificationCompat.Builder(applicationContext, Config.syncChannelId) | ||||
|                 .setContentTitle(context.getString(R.string.loading_notification_title)) | ||||
|                 .setContentText(context.getString(R.string.loading_notification_text)) | ||||
|                 .setOngoing(true) | ||||
|                 .setPriority(PRIORITY_LOW) | ||||
|                 .setChannelId(Config.syncChannelId) | ||||
|                 .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp) | ||||
|             CoroutineScope(Dispatchers.IO).launch { | ||||
|                 val notificationManager = | ||||
|                     applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager | ||||
|  | ||||
|             notificationManager.notify(1, notification.build()) | ||||
|                 val notification = | ||||
|                     NotificationCompat.Builder(applicationContext, Config.syncChannelId) | ||||
|                         .setContentTitle(context.getString(R.string.loading_notification_title)) | ||||
|                         .setContentText(context.getString(R.string.loading_notification_text)) | ||||
|                         .setOngoing(true) | ||||
|                         .setPriority(PRIORITY_LOW) | ||||
|                         .setChannelId(Config.syncChannelId) | ||||
|                         .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp) | ||||
|  | ||||
|             val settings = | ||||
|                 this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) | ||||
|             val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context) | ||||
|             val notifyNewItems = sharedPref.getBoolean("notify_new_items", false) | ||||
|                 notificationManager.notify(1, notification.build()) | ||||
|  | ||||
|             db = Room.databaseBuilder( | ||||
|                 applicationContext, | ||||
|                 AppDatabase::class.java, "selfoss-database" | ||||
|             ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() | ||||
|                 val notifyNewItems = sharedPref.getBoolean("notify_new_items", false) | ||||
|  | ||||
|             val api = SelfossApi( | ||||
|                 this.context, | ||||
|                 null, | ||||
|                 settings.getBoolean("isSelfSignedCert", false), | ||||
|                 sharedPref.getString("api_timeout", "-1")!!.toLong() | ||||
|             ) | ||||
|                 db = Room.databaseBuilder( | ||||
|                     applicationContext, | ||||
|                     AppDatabase::class.java, "selfoss-database" | ||||
|                 ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3) | ||||
|                     .addMigrations(MIGRATION_3_4).build() | ||||
|  | ||||
|             api.allNewItems().enqueue(object : Callback<List<Item>> { | ||||
|                 override fun onFailure(call: Call<List<Item>>, t: Throwable) { | ||||
|                     Timer("", false).schedule(4000) { | ||||
|                         notificationManager.cancel(1) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 override fun onResponse( | ||||
|                     call: Call<List<Item>>, | ||||
|                     response: Response<List<Item>> | ||||
|                 ) { | ||||
|                     storeItems(response, true, notifyNewItems, notificationManager) | ||||
|                 } | ||||
|             }) | ||||
|             api.allReadItems().enqueue(object : Callback<List<Item>> { | ||||
|                 override fun onFailure(call: Call<List<Item>>, t: Throwable) { | ||||
|                     Timer("", false).schedule(4000) { | ||||
|                         notificationManager.cancel(1) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 override fun onResponse( | ||||
|                         call: Call<List<Item>>, | ||||
|                         response: Response<List<Item>> | ||||
|                 ) { | ||||
|                     storeItems(response, false, notifyNewItems, notificationManager) | ||||
|                 } | ||||
|             }) | ||||
|             api.allStarredItems().enqueue(object : Callback<List<Item>> { | ||||
|                 override fun onFailure(call: Call<List<Item>>, t: Throwable) { | ||||
|                     Timer("", false).schedule(4000) { | ||||
|                         notificationManager.cancel(1) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 override fun onResponse( | ||||
|                         call: Call<List<Item>>, | ||||
|                         response: Response<List<Item>> | ||||
|                 ) { | ||||
|                     storeItems(response, false, notifyNewItems, notificationManager) | ||||
|                 } | ||||
|             }) | ||||
|  | ||||
|             thread { | ||||
|                 val actions = db.actionsDao().actions() | ||||
|  | ||||
|                 actions.forEach { action -> | ||||
|                     when { | ||||
|                         action.read -> doAndReportOnFail(api.markItem(action.articleId), action) | ||||
|                         action.unread -> doAndReportOnFail(api.unmarkItem(action.articleId), action) | ||||
|                         action.starred -> doAndReportOnFail(api.starrItem(action.articleId), action) | ||||
|                         action.read -> doAndReportOnFail( | ||||
|                             api.markItem(action.articleId), | ||||
|                             action | ||||
|                         ) | ||||
|                         action.unread -> doAndReportOnFail( | ||||
|                             api.unmarkItem(action.articleId), | ||||
|                             action | ||||
|                         ) | ||||
|                         action.starred -> doAndReportOnFail( | ||||
|                             api.starrItem(action.articleId), | ||||
|                             action | ||||
|                         ) | ||||
|                         action.unstarred -> doAndReportOnFail( | ||||
|                             api.unstarrItem(action.articleId), | ||||
|                             action | ||||
|                         ) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 getAndStoreAllItems(context, api, db) | ||||
|                 SharedItems.updateDatabase(db) | ||||
|                 storeItems(notifyNewItems, notificationManager) | ||||
|             } | ||||
|         } | ||||
|         return Result.success() | ||||
|     } | ||||
|     return Result.success() | ||||
| } | ||||
|  | ||||
|     private fun storeItems(response: Response<List<Item>>, newItems: Boolean, notifyNewItems: Boolean, notificationManager: NotificationManager) { | ||||
|         thread { | ||||
|             if (response.body() != null) { | ||||
|                 val apiItems = (response.body() as ArrayList<Item>) | ||||
|     private fun storeItems(notifyNewItems: Boolean, notificationManager: NotificationManager) { | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|                 val apiItems = SharedItems.items | ||||
|  | ||||
|                 if (newItems) { | ||||
|                     db.itemsDao().deleteAllItems() | ||||
|                 } | ||||
|                 db.itemsDao() | ||||
|                         .insertAllItems(*(apiItems.map { it.toEntity() }).toTypedArray()) | ||||
|  | ||||
|                 val newSize = apiItems.filter { it.unread }.size | ||||
|                 if (newItems && notifyNewItems && newSize > 0) { | ||||
|                 if (notifyNewItems && newSize > 0) { | ||||
|  | ||||
|                     val intent = Intent(context, MainActivity::class.java).apply { | ||||
|                         flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK | ||||
|                     } | ||||
|                     val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0) | ||||
|                     val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|                         PendingIntent.FLAG_IMMUTABLE | ||||
|                     } else { | ||||
|                         0 | ||||
|                     } | ||||
|                     val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags) | ||||
|  | ||||
|                     val newItemsNotification = NotificationCompat.Builder(applicationContext, Config.newItemsChannelId) | ||||
|                     val newItemsNotification = | ||||
|                         NotificationCompat.Builder(applicationContext, Config.newItemsChannelId) | ||||
|                             .setContentTitle(context.getString(R.string.new_items_notification_title)) | ||||
|                             .setContentText(context.getString(R.string.new_items_notification_text, newSize)) | ||||
|                             .setContentText( | ||||
|                                 context.getString( | ||||
|                                     R.string.new_items_notification_text, | ||||
|                                     newSize | ||||
|                                 ) | ||||
|                             ) | ||||
|                             .setPriority(PRIORITY_DEFAULT) | ||||
|                             .setChannelId(Config.newItemsChannelId) | ||||
|                             .setContentIntent(pendingIntent) | ||||
| @@ -161,7 +145,6 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con | ||||
|                     } | ||||
|                 } | ||||
|                 apiItems.map { it.preloadImages(context) } | ||||
|             } | ||||
|             Timer("", false).schedule(4000) { | ||||
|                 notificationManager.cancel(1) | ||||
|             } | ||||
|   | ||||
| @@ -14,6 +14,7 @@ import android.os.Bundle | ||||
| import android.preference.PreferenceManager | ||||
| import android.view.* | ||||
| import android.webkit.* | ||||
| import android.widget.Toast | ||||
| import androidx.browser.customtabs.CustomTabsIntent | ||||
| import com.google.android.material.floatingactionbutton.FloatingActionButton | ||||
| import androidx.fragment.app.Fragment | ||||
| @@ -28,25 +29,17 @@ import apps.amine.bou.readerforselfoss.api.mercury.MercuryApi | ||||
| import apps.amine.bou.readerforselfoss.api.mercury.ParsedContent | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.databinding.FragmentArticleBinding | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_1_2 | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_2_3 | ||||
| import apps.amine.bou.readerforselfoss.persistence.migrations.MIGRATION_3_4 | ||||
| import apps.amine.bou.readerforselfoss.themes.AppColors | ||||
| import apps.amine.bou.readerforselfoss.utils.Config | ||||
| import apps.amine.bou.readerforselfoss.utils.buildCustomTabsIntent | ||||
| import apps.amine.bou.readerforselfoss.utils.* | ||||
| import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.loadMaybeBasicAuth | ||||
| import apps.amine.bou.readerforselfoss.utils.glide.getBitmapInputStream | ||||
| import apps.amine.bou.readerforselfoss.utils.isEmptyOrNullOrNullString | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.openItemUrl | ||||
| import apps.amine.bou.readerforselfoss.utils.shareLink | ||||
| import apps.amine.bou.readerforselfoss.utils.sourceAndDateText | ||||
| import apps.amine.bou.readerforselfoss.utils.succeeded | ||||
| import com.bumptech.glide.Glide | ||||
| import com.bumptech.glide.load.engine.DiskCacheStrategy | ||||
| import com.bumptech.glide.request.RequestOptions | ||||
| @@ -58,7 +51,6 @@ import java.net.MalformedURLException | ||||
| import java.net.URL | ||||
| import java.util.concurrent.ExecutionException | ||||
| import kotlin.collections.ArrayList | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| class ArticleFragment : Fragment() { | ||||
|     private lateinit var pageNumber: Number | ||||
| @@ -181,25 +173,33 @@ class ArticleFragment : Fragment() { | ||||
|                                 false, | ||||
|                                 requireActivity() | ||||
|                             ) | ||||
|                             R.id.unread_action -> if ((context != null && requireContext().isNetworkAccessible(null)) || context == null) { | ||||
|                                 api.unmarkItem(allItems[pageNumber.toInt()].id).enqueue( | ||||
|                                     object : Callback<SuccessResponse> { | ||||
|                                         override fun onResponse( | ||||
|                                             call: Call<SuccessResponse>, | ||||
|                                             response: Response<SuccessResponse> | ||||
|                                         ) { | ||||
|                                         } | ||||
|  | ||||
|                                         override fun onFailure( | ||||
|                                             call: Call<SuccessResponse>, | ||||
|                                             t: Throwable | ||||
|                                         ) { | ||||
|                                         } | ||||
|                                     } | ||||
|                                 ) | ||||
|                             } else { | ||||
|                                 thread { | ||||
|                                     db.actionsDao().insertAllActions(ActionEntity(allItems[pageNumber.toInt()].id, false, true, false, false)) | ||||
|                             R.id.unread_action -> if (context != null) { | ||||
|                                 if (allItems[pageNumber.toInt()].unread) { | ||||
|                                     SharedItems.readItem( | ||||
|                                         context!!, | ||||
|                                         api, | ||||
|                                         db, | ||||
|                                         allItems[pageNumber.toInt()] | ||||
|                                     ) | ||||
|                                     allItems[pageNumber.toInt()].unread = false | ||||
|                                     Toast.makeText( | ||||
|                                         context, | ||||
|                                         R.string.marked_as_read, | ||||
|                                         Toast.LENGTH_LONG | ||||
|                                     ).show() | ||||
|                                 } else { | ||||
|                                     SharedItems.unreadItem( | ||||
|                                         context!!, | ||||
|                                         api, | ||||
|                                         db, | ||||
|                                         allItems[pageNumber.toInt()] | ||||
|                                     ) | ||||
|                                     allItems[pageNumber.toInt()].unread = true | ||||
|                                     Toast.makeText( | ||||
|                                         context, | ||||
|                                         R.string.marked_as_unread, | ||||
|                                         Toast.LENGTH_LONG | ||||
|                                     ).show() | ||||
|                                 } | ||||
|                             } | ||||
|                             else -> Unit | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| @Dao | ||||
| interface ActionsDao { | ||||
|     @Query("SELECT * FROM actions order by id asc") | ||||
|     fun actions(): List<ActionEntity> | ||||
|     suspend fun actions(): List<ActionEntity> | ||||
|  | ||||
|     @Insert(onConflict = OnConflictStrategy.REPLACE) | ||||
|     fun insertAllActions(vararg actions: ActionEntity) | ||||
|   | ||||
| @@ -13,17 +13,17 @@ import androidx.room.Update | ||||
| @Dao | ||||
| interface ItemsDao { | ||||
|     @Query("SELECT * FROM items order by id desc") | ||||
|     fun items(): List<ItemEntity> | ||||
|     suspend fun items(): List<ItemEntity> | ||||
|  | ||||
|     @Insert(onConflict = OnConflictStrategy.REPLACE) | ||||
|     fun insertAllItems(vararg items: ItemEntity) | ||||
|     suspend fun insertAllItems(vararg items: ItemEntity) | ||||
|  | ||||
|     @Query("DELETE FROM items") | ||||
|     fun deleteAllItems() | ||||
|     suspend fun deleteAllItems() | ||||
|  | ||||
|     @Delete | ||||
|     fun delete(item: ItemEntity) | ||||
|     suspend fun delete(item: ItemEntity) | ||||
|  | ||||
|     @Update | ||||
|     fun updateItem(item: ItemEntity) | ||||
|     suspend fun updateItem(item: ItemEntity) | ||||
| } | ||||
| @@ -59,9 +59,9 @@ class AppColors(a: Activity) { | ||||
|         } | ||||
|  | ||||
|         textColor = if (isDarkTheme) { | ||||
|             R.color.md_white_1000 | ||||
|             R.color.white | ||||
|         } else { | ||||
|             R.color.md_grey_900 | ||||
|             R.color.grey_900 | ||||
|         } | ||||
|  | ||||
|         val wrapper = Context::class.java | ||||
|   | ||||
| @@ -7,6 +7,7 @@ import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.graphics.BitmapFactory | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import android.text.Spannable | ||||
| import android.text.style.ClickableSpan | ||||
| import androidx.browser.customtabs.CustomTabsIntent | ||||
| @@ -20,17 +21,23 @@ import apps.amine.bou.readerforselfoss.ReaderActivity | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.utils.customtabs.CustomTabActivityHelper | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.HttpUrl.Companion.toHttpUrlOrNull | ||||
|  | ||||
| fun Context.buildCustomTabsIntent(): CustomTabsIntent { | ||||
|  | ||||
|     val actionIntent = Intent(Intent.ACTION_SEND) | ||||
|     actionIntent.type = "text/plain" | ||||
|     val createPendingShareIntent: PendingIntent = PendingIntent.getActivity( | ||||
|         this, | ||||
|         0, | ||||
|         actionIntent, | ||||
|     val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|         PendingIntent.FLAG_IMMUTABLE | ||||
|     } else { | ||||
|         0 | ||||
|     ) | ||||
|     } | ||||
|     val createPendingShareIntent: PendingIntent = PendingIntent.getActivity( | ||||
|             this, | ||||
|             0, | ||||
|             actionIntent, | ||||
|             pflags | ||||
|         ) | ||||
|  | ||||
|     val intentBuilder = CustomTabsIntent.Builder() | ||||
|  | ||||
| @@ -74,6 +81,7 @@ fun Context.openItemUrlInternally( | ||||
| ) { | ||||
|     if (articleViewer) { | ||||
|         ReaderActivity.allItems = allItems | ||||
|         SharedItems.position = currentItem | ||||
|         val intent = Intent(this, ReaderActivity::class.java) | ||||
|         intent.putExtra("currentItem", currentItem) | ||||
|         app.startActivity(intent) | ||||
| @@ -137,13 +145,13 @@ private fun openInBrowser(linkDecoded: String, app: Activity) { | ||||
| } | ||||
|  | ||||
| fun String.isUrlValid(): Boolean = | ||||
|     HttpUrl.parse(this) != null && Patterns.WEB_URL.matcher(this).matches() | ||||
|     this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches() | ||||
|  | ||||
| fun String.isBaseUrlValid(ctx: Context): Boolean { | ||||
|     val baseUrl = HttpUrl.parse(this) | ||||
|     val baseUrl = this.toHttpUrlOrNull() | ||||
|     var existsAndEndsWithSlash = false | ||||
|     if (baseUrl != null) { | ||||
|         val pathSegments = baseUrl.pathSegments() | ||||
|         val pathSegments = baseUrl.pathSegments | ||||
|         existsAndEndsWithSlash = "" == pathSegments[pathSegments.size - 1] | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,404 @@ | ||||
| package apps.amine.bou.readerforselfoss.utils | ||||
|  | ||||
| import android.content.Context | ||||
| import android.widget.Toast | ||||
| import apps.amine.bou.readerforselfoss.R | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.Item | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SelfossApi | ||||
| import apps.amine.bou.readerforselfoss.api.selfoss.SuccessResponse | ||||
| import apps.amine.bou.readerforselfoss.persistence.database.AppDatabase | ||||
| import apps.amine.bou.readerforselfoss.persistence.entities.ActionEntity | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toEntity | ||||
| import apps.amine.bou.readerforselfoss.utils.network.isNetworkAccessible | ||||
| import apps.amine.bou.readerforselfoss.utils.persistence.toView | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import retrofit2.Call | ||||
| import retrofit2.Callback | ||||
| import retrofit2.Response | ||||
| import java.text.SimpleDateFormat | ||||
| import kotlin.concurrent.thread | ||||
|  | ||||
| /* | ||||
| * Singleton class that contains the articles fetched from Selfoss, it allows sharing the items list | ||||
| * between Activities and Fragments | ||||
| */ | ||||
| object SharedItems { | ||||
|     var items: ArrayList<Item> = arrayListOf<Item>() | ||||
|         get() { | ||||
|             return ArrayList(field) | ||||
|         } | ||||
|         set(value) { | ||||
|             field = ArrayList(value) | ||||
|         } | ||||
|     var focusedItems: ArrayList<Item> = arrayListOf<Item>() | ||||
|         get() { | ||||
|             return ArrayList(field) | ||||
|         } | ||||
|         set(value) { | ||||
|             field = ArrayList(value) | ||||
|         } | ||||
|     var position = 0 | ||||
|         set(value) { | ||||
|             field = when { | ||||
|                 value < 0 -> 0 | ||||
|                 value > items.size -> items.size | ||||
|                 else -> value | ||||
|             } | ||||
|         } | ||||
|     var displayedItems: String = "unread" | ||||
|         set(value) { | ||||
|             field = when (value) { | ||||
|                 "all" -> "all" | ||||
|                 "unread" -> "unread" | ||||
|                 "read" -> "read" | ||||
|                 "starred" -> "starred" | ||||
|                 else -> "all" | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     var searchFilter: String? = null | ||||
|     var sourceIDFilter: Long? = null | ||||
|     var sourceFilter: String? = null | ||||
|     var tagFilter: String? = null | ||||
|     var itemsCaching = false | ||||
|  | ||||
|     var fetchedUnread = false | ||||
|     var fetchedAll = false | ||||
|     var fetchedStarred = false | ||||
|  | ||||
|     var badgeUnread = -1 | ||||
|     var badgeAll = -1 | ||||
|     var badgeStarred = -1 | ||||
|  | ||||
|     /** | ||||
|      * Add new items to the SharedItems list | ||||
|      * | ||||
|      * The new items are considered more updated than the ones already in the list. | ||||
|      * The old items present in the new list are discarded and replaced by the new ones. | ||||
|      * Items are compared according to the selfoss id, which should always be unique. | ||||
|      */ | ||||
|     fun appendNewItems(newItems: ArrayList<Item>) { | ||||
|         var tmpItems = items | ||||
|         if (tmpItems != newItems) { | ||||
|             tmpItems = tmpItems.filter { item -> newItems.find { it.id == item.id } == null } as ArrayList<Item> | ||||
|             tmpItems.addAll(newItems) | ||||
|             items = tmpItems | ||||
|  | ||||
|             sortItems() | ||||
|             getFocusedItems() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun refreshFocusedItems(newItems: ArrayList<Item>) { | ||||
|         val tmpItems = items | ||||
|         tmpItems.removeAll(focusedItems) | ||||
|  | ||||
|         appendNewItems(newItems) | ||||
|     } | ||||
|  | ||||
|     suspend fun clearDBItems(db: AppDatabase) { | ||||
|         db.itemsDao().deleteAllItems() | ||||
|     } | ||||
|  | ||||
|     suspend fun updateDatabase(db: AppDatabase) { | ||||
|         if (itemsCaching) { | ||||
|             if (items.isEmpty()) { | ||||
|                 getFromDB(db) | ||||
|             } | ||||
|             db.itemsDao().deleteAllItems() | ||||
|             db.itemsDao().insertAllItems(*(items.map { it.toEntity() }).toTypedArray()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun filter() { | ||||
|         fun filterSearch(item: Item): Boolean { | ||||
|             return if (!searchFilter.isEmptyOrNullOrNullString()) { | ||||
|                 var matched = item.title.contains(searchFilter.toString(), true) | ||||
|                 matched = matched || item.content.contains(searchFilter.toString(), true) | ||||
|                 matched = matched || item.sourcetitle.contains(searchFilter.toString(), true) | ||||
|                 matched | ||||
|             } else { | ||||
|                 true | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         var tmpItems = focusedItems | ||||
|         if (tagFilter != null) { | ||||
|             tmpItems = tmpItems.filter { it.tags.tags.contains(tagFilter.toString()) } as ArrayList<Item> | ||||
|         } | ||||
|         if (searchFilter != null) { | ||||
|             tmpItems = tmpItems.filter { filterSearch(it) } as ArrayList<Item> | ||||
|         } | ||||
|         if (sourceFilter != null) { | ||||
|             tmpItems = tmpItems.filter { it.sourcetitle == sourceFilter } as ArrayList<Item> | ||||
|         } | ||||
|         focusedItems = tmpItems | ||||
|     } | ||||
|  | ||||
|     private fun getFocusedItems() { | ||||
|         when (displayedItems) { | ||||
|             "all" -> getAll() | ||||
|             "unread" -> getUnRead() | ||||
|             "read" -> getRead() | ||||
|             "starred" -> getStarred() | ||||
|             else -> getUnRead() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun getUnRead() { | ||||
|         displayedItems = "unread" | ||||
|         focusedItems = items.filter { item -> item.unread } as ArrayList<Item> | ||||
|         filter() | ||||
|     } | ||||
|  | ||||
|     fun getRead() { | ||||
|         displayedItems = "read" | ||||
|         focusedItems = items.filter { item -> !item.unread } as ArrayList<Item> | ||||
|         filter() | ||||
|     } | ||||
|  | ||||
|     fun getStarred() { | ||||
|         displayedItems = "starred" | ||||
|         focusedItems = items.filter { item -> item.starred } as ArrayList<Item> | ||||
|         filter() | ||||
|     } | ||||
|  | ||||
|     fun getAll() { | ||||
|         displayedItems = "all" | ||||
|         focusedItems = items | ||||
|         filter() | ||||
|     } | ||||
|  | ||||
|     suspend fun getFromDB(db: AppDatabase) { | ||||
|         if (itemsCaching) { | ||||
|                     val dbItems = db.itemsDao().items().map { it.toView() } as ArrayList<Item> | ||||
|                     appendNewItems(dbItems) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun removeItemAtIndex(index: Int) { | ||||
|         val i = focusedItems[index] | ||||
|         val tmpItems = focusedItems | ||||
|         tmpItems.remove(i) | ||||
|         focusedItems = tmpItems | ||||
|     } | ||||
|  | ||||
|     fun addItemAtIndex(newItem: Item, index: Int) { | ||||
|         val tmpItems = focusedItems | ||||
|         tmpItems.add(index, newItem) | ||||
|         focusedItems = tmpItems | ||||
|     } | ||||
|  | ||||
|     fun readItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) { | ||||
|         if (items.contains(item)) { | ||||
|             position = items.indexOf(item) | ||||
|             readItemAtPosition(app, api, db) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun readItems(db: AppDatabase, ids: List<String>) { | ||||
|         for (id in ids) { | ||||
|             val match = items.filter { it -> it.id == id } | ||||
|             if (match.isNotEmpty() && match.size == 1) { | ||||
|                 position = items.indexOf(match[0]) | ||||
|                 val tmpItems = items | ||||
|                 tmpItems[position].unread = false | ||||
|                 items = tmpItems | ||||
|                 resetDBItem(db) | ||||
|                 badgeUnread-- | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun readItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) { | ||||
|         val i = items[position] | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.markItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                         call: Call<SuccessResponse>, | ||||
|                         response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|  | ||||
|                     val tmpItems = items | ||||
|                     tmpItems[position].unread = false | ||||
|                     items = tmpItems | ||||
|  | ||||
|                     resetDBItem(db) | ||||
|                     getFocusedItems() | ||||
|                     badgeUnread-- | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                     Toast.makeText( | ||||
|                             app, | ||||
|                             app.getString(R.string.cant_mark_read), | ||||
|                             Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                 } | ||||
|             }) | ||||
|         } else if (itemsCaching) { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, true, false, false, false)) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (position > items.size) { | ||||
|             position -= 1 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun unreadItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) { | ||||
|         if (items.contains(item) && !item.unread) { | ||||
|             position = items.indexOf(item) | ||||
|             unreadItemAtPosition(app, api, db) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun unreadItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) { | ||||
|         val i = items[position] | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.unmarkItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|  | ||||
|                     val tmpItems = items | ||||
|                     tmpItems[position].unread = true | ||||
|                     items = tmpItems | ||||
|  | ||||
|                     resetDBItem(db) | ||||
|                     getFocusedItems() | ||||
|                     badgeUnread++ | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure(call: Call<SuccessResponse>, t: Throwable) { | ||||
|                     Toast.makeText( | ||||
|                         app, | ||||
|                         app.getString(R.string.cant_mark_unread), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                 } | ||||
|             }) | ||||
|         } else if (itemsCaching) { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, false, true, false, false)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun starItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) { | ||||
|         if (items.contains(item) && !item.starred) { | ||||
|             position = items.indexOf(item) | ||||
|             starItemAtPosition(app, api, db) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun starItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) { | ||||
|         val i = items[position] | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.starrItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|                     val tmpItems = items | ||||
|                     tmpItems[position].starred = true | ||||
|                     items = tmpItems | ||||
|  | ||||
|                     resetDBItem(db) | ||||
|                     getFocusedItems() | ||||
|                     badgeStarred++ | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     t: Throwable | ||||
|                 ) { | ||||
|                     Toast.makeText( | ||||
|                         app, | ||||
|                         app.getString(R.string.cant_mark_favortie), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                 } | ||||
|             }) | ||||
|         } else { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, false, false, true, false)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun unstarItem(app: Context, api: SelfossApi, db: AppDatabase, item: Item) { | ||||
|         if (items.contains(item) && item.starred) { | ||||
|             position = items.indexOf(item) | ||||
|             unstarItemAtPosition(app, api, db) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun unstarItemAtPosition(app: Context, api: SelfossApi, db: AppDatabase) { | ||||
|         val i = items[position] | ||||
|  | ||||
|         if (app.isNetworkAccessible(null)) { | ||||
|             api.unstarrItem(i.id).enqueue(object : Callback<SuccessResponse> { | ||||
|                 override fun onResponse( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     response: Response<SuccessResponse> | ||||
|                 ) { | ||||
|                     val tmpItems = items | ||||
|                     tmpItems[position].starred = false | ||||
|                     items = tmpItems | ||||
|  | ||||
|                     resetDBItem(db) | ||||
|                     getFocusedItems() | ||||
|                     badgeStarred-- | ||||
|                 } | ||||
|  | ||||
|                 override fun onFailure( | ||||
|                     call: Call<SuccessResponse>, | ||||
|                     t: Throwable | ||||
|                 ) { | ||||
|                     Toast.makeText( | ||||
|                         app, | ||||
|                         app.getString(R.string.cant_unmark_favortie), | ||||
|                         Toast.LENGTH_SHORT | ||||
|                     ).show() | ||||
|                 } | ||||
|             }) | ||||
|         } else { | ||||
|             thread { | ||||
|                 db.actionsDao().insertAllActions(ActionEntity(i.id, false, false, false, true)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun resetDBItem(db: AppDatabase) { | ||||
|         if (itemsCaching) { | ||||
|             val i = items[position] | ||||
|             CoroutineScope(Dispatchers.IO).launch { | ||||
|                 db.itemsDao().delete(i.toEntity()) | ||||
|                 db.itemsDao().insertAllItems(i.toEntity()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun unreadItemStatusAtIndex(position: Int): Boolean { | ||||
|         return focusedItems[position].unread | ||||
|     } | ||||
|  | ||||
|     fun computeBadges() { | ||||
|         badgeUnread = items.filter { item -> item.unread }.size | ||||
|         badgeStarred = items.filter { item -> item.starred }.size | ||||
|         badgeAll = items.size | ||||
|     } | ||||
|  | ||||
|     private fun sortItems() { | ||||
|         val tmpItems = ArrayList(items.sortedByDescending { SimpleDateFormat(Config.dateTimeFormatter).parse((it.datetime)) }) | ||||
|         items = tmpItems | ||||
|     } | ||||
| } | ||||
| @@ -3,7 +3,8 @@ package apps.amine.bou.readerforselfoss.utils.network | ||||
| import android.content.Context | ||||
| import android.graphics.Color | ||||
| import android.net.ConnectivityManager | ||||
| import android.net.NetworkInfo | ||||
| import android.net.NetworkCapabilities | ||||
| import android.os.Build | ||||
| import android.view.View | ||||
| import android.widget.TextView | ||||
| import apps.amine.bou.readerforselfoss.R | ||||
| @@ -14,9 +15,7 @@ var view: View? = null | ||||
| lateinit var s: Snackbar | ||||
|  | ||||
| fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boolean { | ||||
|     val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | ||||
|     val activeNetwork: NetworkInfo? = cm.activeNetworkInfo | ||||
|     val networkIsAccessible = activeNetwork != null && activeNetwork.isConnectedOrConnecting | ||||
|     val networkIsAccessible = isNetworkAvailable(this) | ||||
|  | ||||
|     if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) { | ||||
|         view = v | ||||
| @@ -42,4 +41,24 @@ fun Context.isNetworkAccessible(v: View?, overrideOffline: Boolean = false): Boo | ||||
|         s.dismiss() | ||||
|     } | ||||
|     return if(overrideOffline) overrideOffline else networkIsAccessible | ||||
| } | ||||
|  | ||||
| fun isNetworkAvailable(context: Context): Boolean { | ||||
|     val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager | ||||
|  | ||||
|      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|          val network = connectivityManager.activeNetwork ?: return false | ||||
|          val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false | ||||
|  | ||||
|          return when { | ||||
|              networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true | ||||
|              networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true | ||||
|              networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true | ||||
|              networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true | ||||
|              else -> false | ||||
|          } | ||||
|     } else { | ||||
|         val network = connectivityManager.activeNetworkInfo ?: return false | ||||
|          return network.isConnectedOrConnecting | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								app/src/main/res/color/ic_menu_heart_color.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/src/main/res/color/ic_menu_heart_color.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <item android:state_selected="true" | ||||
|         android:color="@color/red"/> | ||||
|  | ||||
|     <item android:state_selected="false" | ||||
|         android:color="?android:attr/textColorPrimary" /> | ||||
| </selector> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_baseline_white_eye_24dp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/ic_baseline_white_eye_24dp.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <vector android:height="24dp" android:tint="#FFFFFF" | ||||
|     android:viewportHeight="24" android:viewportWidth="24" | ||||
|     android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <path android:fillColor="@android:color/white" android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/> | ||||
| </vector> | ||||
| @@ -1,5 +0,0 @@ | ||||
| <vector android:height="24dp" android:tint="#FFFFFF" | ||||
|     android:viewportHeight="24.0" android:viewportWidth="24.0" | ||||
|     android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <path android:fillColor="#FF000000" android:pathData="M20,4L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,6c0,-1.11 -0.89,-2 -2,-2zM8.5,15L7.3,15l-2.55,-3.5L4.75,15L3.5,15L3.5,9h1.25l2.5,3.5L7.25,9L8.5,9v6zM13.5,10.26L11,10.26v1.12h2.5v1.26L11,12.64v1.11h2.5L13.5,15h-4L9.5,9h4v1.26zM20.5,14c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1L14.5,9h1.25v4.51h1.13L16.88,9.99h1.25v3.51h1.12L19.25,9h1.25v5z"/> | ||||
| </vector> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/drawable/ic_menu_heart_60dp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/drawable/ic_menu_heart_60dp.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <vector android:height="54.751434dp" android:viewportHeight="18.756023" | ||||
|     android:viewportWidth="20.554007" android:width="60dp" xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <path android:fillColor="#FFFFFFFF" | ||||
|         android:pathData="m5.7968,14.6109c-2.7907,-2.7367 -4.4957,-4.7131 -5.018,-5.8165 -2.102,-4.4408 0.2424,-8.7943 4.7357,-8.7943 1.635,0 2.7056,0.425 3.9688,1.5755l0.7937,0.723 0.7937,-0.723c1.2631,-1.1505 2.3337,-1.5755 3.9688,-1.5755 4.4933,0 6.8377,4.3535 4.7357,8.7943 -0.5223,1.1035 -2.2274,3.0799 -5.018,5.8165 -2.3248,2.2798 -4.3409,4.1451 -4.4802,4.1451 -0.1393,0 -2.1554,-1.8653 -4.4802,-4.1451z" android:strokeWidth="0.0933392"/> | ||||
| </vector> | ||||
| @@ -1,7 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <RelativeLayout | ||||
| <androidx.drawerlayout.widget.DrawerLayout | ||||
|     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:id="@+id/drawerContainer" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     tools:context="apps.amine.bou.readerforselfoss.HomeActivity" | ||||
| @@ -36,51 +37,43 @@ | ||||
|  | ||||
|                 </com.google.android.material.appbar.AppBarLayout> | ||||
|  | ||||
|                 <FrameLayout | ||||
|                     android:id="@+id/drawer_layout" | ||||
|                     xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|                     xmlns:tools="http://schemas.android.com/tools" | ||||
|                 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout | ||||
|                     android:id="@+id/swipeRefreshLayout" | ||||
|                     android:layout_width="match_parent" | ||||
|                     android:layout_height="match_parent"> | ||||
|  | ||||
|                     <androidx.swiperefreshlayout.widget.SwipeRefreshLayout | ||||
|                         android:id="@+id/swipeRefreshLayout" | ||||
|                     <LinearLayout | ||||
|                         android:layout_width="match_parent" | ||||
|                         android:layout_height="match_parent"> | ||||
|                         android:layout_height="match_parent" | ||||
|                         android:orientation="vertical" | ||||
|                         android:background="?android:attr/windowBackground"> | ||||
|  | ||||
|                         <LinearLayout | ||||
|                         <TextView | ||||
|                             android:id="@+id/emptyText" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="match_parent" | ||||
|                             android:orientation="vertical" | ||||
|                             android:background="?android:attr/windowBackground"> | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:gravity="center_horizontal" | ||||
|                             android:paddingTop="100dp" | ||||
|                             android:text="@string/nothing_here" | ||||
|                             android:textAlignment="center" | ||||
|                             android:textAppearance="@style/TextAppearance.AppCompat.Headline" | ||||
|                             android:background="@android:color/transparent" | ||||
|                             android:visibility="gone" /> | ||||
|  | ||||
|                             <TextView | ||||
|                                 android:id="@+id/emptyText" | ||||
|                                 android:layout_width="match_parent" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:gravity="fill" | ||||
|                                 android:paddingTop="100dp" | ||||
|                                 android:text="@string/nothing_here" | ||||
|                                 android:textAlignment="center" | ||||
|                                 android:textAppearance="@style/TextAppearance.AppCompat.Headline" | ||||
|                                 android:background="@android:color/transparent" | ||||
|                                 android:visibility="gone" /> | ||||
|                         <androidx.recyclerview.widget.RecyclerView | ||||
|                             android:id="@+id/recyclerView" | ||||
|                             android:layout_width="match_parent" | ||||
|                             android:layout_height="wrap_content" | ||||
|                             android:background="@android:color/transparent" | ||||
|                             android:clipToPadding="false" | ||||
|                             android:paddingBottom="60dp" | ||||
|                             android:scrollbars="vertical" | ||||
|                             app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||||
|                             tools:listitem="@layout/list_item"/> | ||||
|                     </LinearLayout> | ||||
|  | ||||
|                             <androidx.recyclerview.widget.RecyclerView | ||||
|                                 android:id="@+id/recyclerView" | ||||
|                                 android:layout_width="match_parent" | ||||
|                                 android:layout_height="wrap_content" | ||||
|                                 android:background="@android:color/transparent" | ||||
|                                 android:clipToPadding="false" | ||||
|                                 android:paddingBottom="60dp" | ||||
|                                 android:scrollbars="vertical" | ||||
|                                 app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||||
|                                 tools:listitem="@layout/list_item"/> | ||||
|                         </LinearLayout> | ||||
|                 </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> | ||||
|  | ||||
|                     </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> | ||||
|  | ||||
|                 </FrameLayout> | ||||
|             </LinearLayout> | ||||
|  | ||||
|         </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
| @@ -90,4 +83,11 @@ | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="60dp"/> | ||||
|     </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
| </RelativeLayout> | ||||
|  | ||||
|     <com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView | ||||
|         android:id="@+id/mainDrawer" | ||||
|         android:layout_width="wrap_content" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_gravity="start" | ||||
|         android:fitsSystemWindows="true" /> | ||||
| </androidx.drawerlayout.widget.DrawerLayout> | ||||
| @@ -92,20 +92,22 @@ | ||||
|                 app:layout_constraintRight_toRightOf="parent" | ||||
|                 app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate"> | ||||
|  | ||||
|                 <com.like.LikeButton | ||||
|                 <ImageButton | ||||
|                     android:id="@+id/favButton" | ||||
|                     android:layout_width="35dp" | ||||
|                     android:layout_height="35dp" | ||||
|                     android:layout_alignParentEnd="true" | ||||
|                     android:layout_alignParentRight="true" | ||||
|  | ||||
|                     android:layout_centerVertical="true" | ||||
|                     android:layout_marginEnd="8dp" | ||||
|                     android:layout_marginRight="8dp" | ||||
|                     android:adjustViewBounds="true" | ||||
|                     android:background="@android:color/transparent" | ||||
|                     android:elevation="5dp" | ||||
|                     android:padding="4dp" | ||||
|                     app:icon_size="22dp" | ||||
|                     app:icon_type="heart" /> | ||||
|                     android:scaleType="centerCrop" | ||||
|                     app:srcCompat="@drawable/ic_menu_heart_60dp" | ||||
|                     app:tint="@color/ic_menu_heart_color" /> | ||||
|  | ||||
|                 <ImageButton | ||||
|                     android:id="@+id/shareBtn" | ||||
| @@ -121,8 +123,8 @@ | ||||
|                     android:elevation="5dp" | ||||
|                     android:padding="4dp" | ||||
|                     android:scaleType="centerCrop" | ||||
|                     android:src="@drawable/ic_share_black_24dp" | ||||
|                     android:tint="?android:attr/textColorPrimary" /> | ||||
|                     app:srcCompat="@drawable/ic_share_black_24dp" | ||||
|                     app:tint="?android:attr/textColorPrimary" /> | ||||
|  | ||||
|                 <ImageButton | ||||
|                     android:id="@+id/browserBtn" | ||||
| @@ -138,8 +140,8 @@ | ||||
|                     android:elevation="5dp" | ||||
|                     android:padding="4dp" | ||||
|                     android:scaleType="centerCrop" | ||||
|                     android:src="@drawable/ic_open_in_browser_black_24dp" | ||||
|                     android:tint="?android:attr/textColorPrimary" /> | ||||
|                     app:srcCompat="@drawable/ic_open_in_browser_black_24dp" | ||||
|                     app:tint="?android:attr/textColorPrimary" /> | ||||
|  | ||||
|             </RelativeLayout> | ||||
|  | ||||
|   | ||||
| @@ -15,14 +15,8 @@ | ||||
|         android:title="@string/reader_text_align_justify" /> | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/unsave" | ||||
|         android:icon="@drawable/heart_on" | ||||
|         android:title="@string/remove_to_favs_reader" | ||||
|         android:visible="true" | ||||
|         app:showAsAction="ifRoom" /> | ||||
|     <item | ||||
|         android:id="@+id/save" | ||||
|         android:icon="@drawable/heart_off" | ||||
|         android:id="@+id/star" | ||||
|         android:icon="@drawable/ic_menu_heart_60dp" | ||||
|         android:title="@string/add_to_favs_reader" | ||||
|         android:visible="true" | ||||
|         app:showAsAction="ifRoom" /> | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  | ||||
|     <item | ||||
|         android:id="@+id/unread_action" | ||||
|         android:icon="@drawable/ic_fiber_new_white_24dp" | ||||
|         android:icon="@drawable/ic_baseline_white_eye_24dp" | ||||
|         android:title="@string/unmark" | ||||
|         app:showAsAction="ifRoom" /> | ||||
|  | ||||
| @@ -17,7 +17,6 @@ | ||||
|     <item | ||||
|         android:id="@+id/open_action" | ||||
|         android:icon="@drawable/ic_open_in_browser_white_24dp" | ||||
|         android:iconTint="@color/white" | ||||
|         android:title="@string/reader_action_open" | ||||
|         app:showAsAction="ifRoom" /> | ||||
|  | ||||
|   | ||||
| @@ -7,6 +7,7 @@ | ||||
|     <color name="pink">#FFe91e63</color> | ||||
|     <color name="white">#FFFFFFFF</color> | ||||
|     <color name="black">#FF000000</color> | ||||
|     <color name="red">#FF0000</color> | ||||
|     <color name="refresh_progress_1">@color/colorAccentDark</color> | ||||
|     <color name="refresh_progress_2">@color/colorAccent</color> | ||||
|     <color name="refresh_progress_3">@color/pink</color> | ||||
|   | ||||
| @@ -4,30 +4,34 @@ | ||||
|         <item name="android:windowBackground">@drawable/background_splash</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="NoBar" parent="MaterialDrawerTheme.Light"> | ||||
|     <style name="NoBar" parent="Theme.MaterialComponents.Light.NoActionBar"> | ||||
|         <item name="colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="colorAccent">@color/colorAccent</item> | ||||
|         <item name="colorAccentDark">@color/colorAccentDark</item> | ||||
|         <item name="cardBackgroundColor">@color/white</item> | ||||
|         <item name="android:colorBackground">@color/md_grey_50</item> | ||||
|         <item name="android:textColorPrimary">@color/md_grey_900</item> | ||||
|         <item name="android:textColorSecondary">@color/md_grey_400</item> | ||||
|         <item name="material_drawer_header_selection_text">@color/md_grey_900</item> | ||||
|         <item name="android:colorBackground">@color/grey_50</item> | ||||
|         <item name="colorSurface">@color/grey_50</item> | ||||
|         <item name="android:textColorPrimary">@color/grey_900</item> | ||||
|         <item name="android:textColorSecondary">@color/grey_400</item> | ||||
|         <item name="materialDrawerStyle">@style/Widget.MaterialDrawerStyle</item> | ||||
|         <item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item> | ||||
|         <item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Light</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="NoBarDark" parent="MaterialDrawerTheme"> | ||||
|     <style name="NoBarDark" parent="Theme.MaterialComponents.DayNight.NoActionBar"> | ||||
|         <item name="colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="colorAccent">@color/colorAccent</item> | ||||
|         <item name="colorAccentDark">@color/colorAccentDark</item> | ||||
|         <item name="cardBackgroundColor">@color/md_grey_800</item> | ||||
|         <item name="cardBackgroundColor">@color/grey_800</item> | ||||
|         <item name="android:colorBackground">@color/darkBackground</item> | ||||
|         <item name="bnbBackgroundColor">@color/md_grey_900</item> | ||||
|         <item name="android:textColorPrimary">@color/md_white_1000</item> | ||||
|         <item name="android:textColorSecondary">@color/md_grey_600</item> | ||||
|         <item name="material_drawer_header_selection_text">@color/md_grey_900</item> | ||||
|         <item name="colorSurface">@color/darkBackground</item> | ||||
|         <item name="bnbBackgroundColor">@color/grey_900</item> | ||||
|         <item name="android:textColorPrimary">@color/white</item> | ||||
|         <item name="android:textColorSecondary">@color/grey_600</item> | ||||
|         <item name="materialDrawerStyle">@style/Widget.MaterialDrawerStyle</item> | ||||
|         <item name="materialDrawerHeaderStyle">@style/Widget.MaterialDrawerHeaderStyle</item> | ||||
|         <item name="toolbarPopupTheme">@style/ThemeOverlay.AppCompat.Dark</item> | ||||
|     </style> | ||||
|  | ||||
| @@ -35,7 +39,6 @@ | ||||
|     <style name="ToolBarStyle" parent="Theme.AppCompat"> | ||||
|         <item name="android:textColorPrimary">@color/white</item> | ||||
|         <item name="android:textColorSecondary">@color/white</item> | ||||
|         <item name="material_drawer_header_selection_text">@color/md_grey_900</item> | ||||
|         <item name="actionMenuTextColor">@color/white</item> | ||||
|         <!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item> | ||||
|         <item name="drawerArrowStyle">@style/DrawerArrowStyle</item>--> | ||||
|   | ||||
							
								
								
									
										16
									
								
								build.gradle
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								build.gradle
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| buildscript { | ||||
|     ext { | ||||
|         kotlin_version = '1.4.21' | ||||
|         kotlin_version = '1.5.31' | ||||
|         android_version = '1.0.0' | ||||
|         androidx_version = '1.1.0-alpha05' | ||||
|         lifecycle_version = '2.2.0-alpha01' | ||||
| @@ -17,7 +17,7 @@ buildscript { | ||||
|         } | ||||
|     } | ||||
|     dependencies { | ||||
|         classpath 'com.android.tools.build:gradle:4.1.1' | ||||
|         classpath 'com.android.tools.build:gradle:7.0.3' | ||||
|         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||
|     } | ||||
| } | ||||
| @@ -37,14 +37,4 @@ task clean(type: Delete) { | ||||
|     delete rootProject.buildDir | ||||
| } | ||||
|  | ||||
| project.ext.preDexLibs = !project.hasProperty('disablePreDex') | ||||
|  | ||||
| subprojects { | ||||
|     project.plugins.whenPluginAdded { plugin -> | ||||
|         if ("com.android.build.gradle.AppPlugin" == plugin.class.name) { | ||||
|             project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs | ||||
|         } else if ("com.android.build.gradle.LibraryPlugin" == plugin.class.name) { | ||||
|             project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs | ||||
|         } | ||||
|     } | ||||
| } | ||||
| project.ext.preDexLibs = !project.hasProperty('disablePreDex') | ||||
| @@ -16,6 +16,5 @@ org.gradle.jvmargs=-Xmx1536m | ||||
| # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects | ||||
| # org.gradle.parallel=true | ||||
| org.gradle.caching=true | ||||
| android.enableD8=true | ||||
| android.useAndroidX=true | ||||
| android.enableJetifier=true | ||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip | ||||
|   | ||||
		Reference in New Issue
	
	Block a user