diff --git a/CHANGELOG.md b/CHANGELOG.md index f6db0d1..6cbcfac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +**1.5.0.3** + +- Added a drawer for filtering sources and tags. + **1.5.0.2** - If the content in the article viewer is empty, the article will open in a custom tab. diff --git a/app/build.gradle b/app/build.gradle index e824ecd..ff6975c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -113,6 +113,12 @@ dependencies { // For the article reader compile 'com.klinkerapps:drag-dismiss-activity:1.4.0' + // Drawer + compile('com.mikepenz:materialdrawer:5.9.2@aar') { + transitive = true + } + compile 'com.anupcowkur:reservoir:3.1.0' + } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 15c5231..e779c4c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,7 +31,8 @@ - + = ArrayList() @@ -77,6 +92,10 @@ class HomeActivity : AppCompatActivity() { private var tabStarred: BottomBarTab? = null private var mFirebaseRemoteConfig: FirebaseRemoteConfig? = null private var fullHeightCards: Boolean = false + private var toolbar: Toolbar? = null + private var drawer: Drawer? = null + + data class DrawerData(val tags: List?, val sources: List?) private fun handleSharedPrefs() { clickBehavior = this.sharedPref!!.getBoolean("tab_on_tap", false) @@ -91,6 +110,8 @@ class HomeActivity : AppCompatActivity() { override fun onResume() { super.onResume() + handleDrawerItems() + sharedPref = PreferenceManager.getDefaultSharedPreferences(this) val settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE) @@ -110,13 +131,150 @@ class HomeActivity : AppCompatActivity() { getElementsAccordingToTab() } + fun handleDrawer() { + + drawer = DrawerBuilder() + .withActivity(this) + .withRootView(R.id.drawer_layout) + .withToolbar(toolbar!!) + .withActionBarDrawerToggle(true) + .withActionBarDrawerToggleAnimated(true) + .withShowDrawerOnFirstLaunch(true) + .build() + + } + + fun handleDrawerItems() { + fun handleDrawerData(maybeDrawerData: DrawerData?, loadedFromCache: Boolean = false) { + fun handleTags(maybeTags: List?) { + if (maybeTags == null) { + if (loadedFromCache) + drawer!!.addItem(PrimaryDrawerItem().withName("Error loading tags...")) + } + else { + for (tag in maybeTags) { + val gd: GradientDrawable = GradientDrawable() + gd.setColor(Color.parseColor(tag.color)) + gd.shape = GradientDrawable.RECTANGLE + gd.setSize(30, 30) + gd.cornerRadius = 30F + drawer!!.addItem( + PrimaryDrawerItem() + .withName(tag.tag) + .withIdentifier(longHash(tag.tag)) + .withIcon(gd) + .withOnDrawerItemClickListener { _, _, _ -> + getElementsAccordingToTab(maybeTagFilter = tag) + true + } + ) + } + } + + } + + fun handleSources(maybeSources: List?) { + if (maybeSources == null) { + if (loadedFromCache) + drawer!!.addItem(PrimaryDrawerItem().withName("Error loading sources...")) + } + else + for (tag in maybeSources) + drawer!!.addItem( + PrimaryDrawerItem() + .withName(tag.title) + .withIdentifier(tag.id.toLong()) + .withOnDrawerItemClickListener { _, _, _ -> + getElementsAccordingToTab(maybeSourceFilter = tag) + true + } + ) + + } + + drawer!!.removeAllItems() + if (maybeDrawerData != null) { + drawer!!.addItem(SecondaryDrawerItem().withName("Tags").withIdentifier(DRAWER_ID_TAGS).withSelectable(false)) + handleTags(maybeDrawerData.tags) + drawer!!.addItem(DividerDrawerItem()) + drawer!!.addItem(SecondaryDrawerItem().withName("Sources").withIdentifier(DRAWER_ID_TAGS).withSelectable(false)) + handleSources(maybeDrawerData.sources) + if (!loadedFromCache) + Reservoir.putAsync("drawerData", maybeDrawerData, object : ReservoirPutCallback { + override fun onSuccess() {} + + override fun onFailure(p0: Exception?) { + Toast.makeText(this@HomeActivity, "Couldn't cache your drawer data", Toast.LENGTH_SHORT).show() + } + + }) + } else { + if (!loadedFromCache) { + drawer!!.addItem(SecondaryDrawerItem().withName("No tags loaded").withIdentifier(DRAWER_ID_TAGS).withSelectable(false)) + drawer!!.addItem(SecondaryDrawerItem().withName("No sources loaded").withIdentifier(DRAWER_ID_SOURCES).withSelectable(false)) + } + } + + } + + fun drawerApiCalls(maybeDrawerData: DrawerData?) { + var tags: List? = null + var sources: List? = null + + fun sourcesApiCall() { + api!!.sources.enqueue(object: Callback> { + override fun onResponse(call: Call>?, response: Response>) { + sources = response.body() + val apiDrawerData = DrawerData(tags, sources) + if (maybeDrawerData == null || (maybeDrawerData != null && maybeDrawerData != apiDrawerData)) + handleDrawerData(apiDrawerData) + } + + override fun onFailure(call: Call>?, t: Throwable?) { + + } + + }) + } + + api!!.tags.enqueue(object: Callback> { + override fun onResponse(call: Call>, response: Response>) { + tags = response.body() + sourcesApiCall() + } + + override fun onFailure(call: Call>?, t: Throwable?) { + sourcesApiCall() + } + + }) + } + + drawer!!.addItem(SecondaryDrawerItem().withName("Loading ...").withSelectable(false)) + + val resultType = object : TypeToken() {}.type + Reservoir.getAsync("drawerData", resultType, object: ReservoirGetCallback { + override fun onSuccess(maybeDrawerData: DrawerData?) { + handleDrawerData(maybeDrawerData, loadedFromCache = true) + drawerApiCalls(maybeDrawerData) + } + + override fun onFailure(p0: Exception?) { + drawerApiCalls(null) + } + + }) + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_home) + toolbar = findViewById(R.id.toolbar) as Toolbar? + setSupportActionBar(toolbar) + if (savedInstanceState == null) { val promptView = findViewById(R.id.prompt_view) as DefaultLayoutPromptView - Amplify.getSharedInstance().promptIfReady(promptView) } @@ -130,6 +288,8 @@ class HomeActivity : AppCompatActivity() { mBottomBar = findViewById(R.id.bottomBar) as BottomBar + handleDrawer() + // TODO: clean this hack val listenerAlreadySet = booleanArrayOf(false) mBottomBar!!.setOnTabSelectListener { tabId -> @@ -228,20 +388,20 @@ class HomeActivity : AppCompatActivity() { } } - private fun getElementsAccordingToTab() { + private fun getElementsAccordingToTab(maybeTagFilter: Tag? = null, maybeSourceFilter: Sources? = null) { items = ArrayList() when (elementsShown) { - UNREAD_SHOWN -> getUnRead() - READ_SHOWN -> getRead() - FAV_SHOWN -> getStarred() - else -> getUnRead() + UNREAD_SHOWN -> getUnRead(maybeTagFilter, maybeSourceFilter) + READ_SHOWN -> getRead(maybeTagFilter, maybeSourceFilter) + FAV_SHOWN -> getStarred(maybeTagFilter, maybeSourceFilter) + else -> getUnRead(maybeTagFilter, maybeSourceFilter) } } - private fun getUnRead() { + private fun getUnRead(maybeTagFilter: Tag? = null, maybeSourceFilter: Sources? = null) { elementsShown = UNREAD_SHOWN - api!!.unreadItems.enqueue(object : Callback> { + api!!.unreadItems(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong()).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { if (response.body() != null && response.body()!!.isNotEmpty()) { items = response.body() as ArrayList @@ -259,9 +419,9 @@ class HomeActivity : AppCompatActivity() { }) } - private fun getRead() { + private fun getRead(maybeTagFilter: Tag? = null, maybeSourceFilter: Sources? = null) { elementsShown = READ_SHOWN - api!!.readItems.enqueue(object : Callback> { + api!!.readItems(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong()).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { if (response.body() != null && response.body()!!.isNotEmpty()) { items = response.body() as ArrayList @@ -279,9 +439,9 @@ class HomeActivity : AppCompatActivity() { }) } - private fun getStarred() { + private fun getStarred(maybeTagFilter: Tag? = null, maybeSourceFilter: Sources? = null) { elementsShown = FAV_SHOWN - api!!.starredItems.enqueue(object : Callback> { + api!!.starredItems(maybeTagFilter?.tag, maybeSourceFilter?.id?.toLong()).enqueue(object : Callback> { override fun onResponse(call: Call>, response: Response>) { if (response.body() != null && response.body()!!.isNotEmpty()) { items = response.body() as ArrayList diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt index 9e2f304..62a8e6e 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/MyApp.kt @@ -4,6 +4,8 @@ import android.support.multidex.MultiDexApplication import com.crashlytics.android.Crashlytics import com.github.stkent.amplify.tracking.Amplify import io.fabric.sdk.android.Fabric +import com.anupcowkur.reservoir.Reservoir +import java.io.IOException class MyApp : MultiDexApplication() { @@ -16,5 +18,11 @@ class MyApp : MultiDexApplication() { .setFeedbackEmailAddress(getString(R.string.feedback_email)) .setAlwaysShow(BuildConfig.DEBUG) .applyAllDefaultRules() + + try { + Reservoir.init(this, 4096) //in bytes + } catch (e: IOException) { + //failure + } } } \ No newline at end of file diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt index eec5a62..baced67 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossApi.kt @@ -72,17 +72,17 @@ class SelfossApi(c: Context) { return service.loginToSelfoss(config.userLogin, config.userPassword) } - val readItems: Call> - get() = getItems("read") + fun readItems(tag: String?, sourceId: Long?): Call> = + getItems("read", tag, sourceId) - val unreadItems: Call> - get() = getItems("unread") + fun unreadItems(tag: String?, sourceId: Long?): Call> = + getItems("unread", tag, sourceId) - val starredItems: Call> - get() = getItems("starred") + fun starredItems(tag: String?, sourceId: Long?): Call> = + getItems("starred", tag, sourceId) - private fun getItems(type: String): Call> { - return service.getItems(type, userName, password) + private fun getItems(type: String, tag: String?, sourceId: Long?): Call> { + return service.getItems(type, tag, sourceId, userName, password) } fun markItem(itemId: String): Call { diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt index a67510f..0038934 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossModels.kt @@ -17,7 +17,7 @@ private fun constructUrl(config: Config?, path: String, file: String): String { } -data class Tag(val tag: String, val color: String, val unread: Int) +data class Tag(val tag: String, val color: String) class SuccessResponse(val success: Boolean) { val isSuccess: Boolean diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt index b281007..3b48401 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/api/selfoss/SelfossService.kt @@ -16,7 +16,11 @@ internal interface SelfossService { fun loginToSelfoss(@Query("username") username: String, @Query("password") password: String): Call @GET("items") - fun getItems(@Query("type") type: String, @Query("username") username: String, @Query("password") password: String): Call> + fun getItems(@Query("type") type: String, + @Query("tag") tag: String?, + @Query("source") source: Long?, + @Query("username") username: String, + @Query("password") password: String): Call> @POST("mark/{id}") fun markAsRead(@Path("id") id: String, @Query("username") username: String, @Query("password") password: String): Call diff --git a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt index 0706115..3381bcd 100644 --- a/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt +++ b/app/src/main/java/apps/amine/bou/readerforselfoss/utils/AppUtils.kt @@ -85,4 +85,15 @@ private fun isThereAnUpdate(settings: SharedPreferences, editor: SharedPreferenc alertDialog.show() } +} + +fun longHash(string: String): Long { + var h = 98764321261L + val l = string.length + val chars = string.toCharArray() + + for (i in 0..l - 1) { + h = 31 * h + chars[i].toLong() + } + return h } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 78dd711..89677b4 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -5,6 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context="apps.amine.bou.readerforselfoss.HomeActivity" + android:fitsSystemWindows="true" xmlns:app="http://schemas.android.com/apk/res-auto"> - - - - + android:layout_height="match_parent" + android:orientation="vertical"> + + + + + + + + + + + + + + + + + - \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e06c743..0f1e291 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -13,4 +13,20 @@ @drawable/background_splash + + + + +