--- a/manage_feeds/build.gradle.kts Wed Jun 26 15:11:28 2019 -0700
+++ b/manage_feeds/build.gradle.kts Wed Jun 26 15:35:06 2019 -0700
@@ -67,6 +67,8 @@
val geekdroidExt = GEEKDROID_PROJECT_DIR?.let { "" } ?: "aar"
implementation(group = "com.geekorum", name = "geekdroid", version = "0.0.1", ext = geekdroidExt)
+ implementation("androidx.activity:activity-ktx:1.0.0-beta01")
+
// androidx UI
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
implementation("androidx.recyclerview:recyclerview:1.0.0")
@@ -78,6 +80,7 @@
val lifecycleVersion: String by rootProject.extra
implementation("androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
implementation(project(":htmlparsers"))
implementation(project(":webapi"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/manage_feeds/src/androidTest/java/com/geekorum/ttrss/manage_feeds/add_feed/AddFeedActivityTest.kt Wed Jun 26 15:35:06 2019 -0700
@@ -0,0 +1,65 @@
+/*
+ * Geekttrss is a RSS feed reader application on the Android Platform.
+ *
+ * Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekttrss.
+ *
+ * Geekttrss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekttrss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekttrss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.ttrss.manage_feeds.add_feed
+
+import android.app.Activity
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.launchActivity
+import androidx.test.espresso.Espresso.onView
+import androidx.test.espresso.action.ViewActions.click
+import androidx.test.espresso.assertion.ViewAssertions.matches
+import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
+import androidx.test.espresso.matcher.ViewMatchers.withId
+import androidx.test.espresso.matcher.ViewMatchers.withText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.geekorum.ttrss.manage_feeds.R
+import com.geekorum.geekdroid.R as geekdroidR
+import com.google.common.truth.Truth.assertWithMessage
+import org.hamcrest.Matchers.allOf
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AddFeedActivityTest {
+
+ @Test
+ fun testThatWeCanLaunchTheActivity() {
+ val scenario: ActivityScenario<AddFeedActivity> = launchActivity()
+ scenario.use {
+ // remember the activity. We should not but we are explicitely testing that it is finishing
+ lateinit var currentActivity: Activity
+ scenario.onActivity {
+ currentActivity = it
+ }
+
+ // Check that the toolbar is there
+ onView(withId(R.id.toolbar))
+ .check(matches(isCompletelyDisplayed()))
+
+ // click outside
+ onView(withId(geekdroidR.id.touch_outside))
+ .perform(click())
+
+ assertWithMessage("The activity should finish on outside touch")
+ .that(currentActivity.isFinishing).isTrue()
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/manage_feeds/src/main/java/com/geekorum/ttrss/manage_feeds/add_feed/AddFeedActivity.kt Wed Jun 26 15:35:06 2019 -0700
@@ -0,0 +1,162 @@
+/*
+ * Geekttrss is a RSS feed reader application on the Android Platform.
+ *
+ * Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+ *
+ * This file is part of Geekttrss.
+ *
+ * Geekttrss is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Geekttrss is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Geekttrss. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.geekorum.ttrss.manage_feeds.add_feed
+
+import android.accounts.Account
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.activity.viewModels
+import androidx.lifecycle.Observer
+import androidx.lifecycle.lifecycleScope
+import com.geekorum.geekdroid.app.BottomSheetDialogActivity
+import com.geekorum.geekdroid.app.lifecycle.EventObserver
+import com.geekorum.geekdroid.dagger.DaggerDelegateViewModelsFactory
+import com.geekorum.ttrss.R
+import com.geekorum.ttrss.htmlparsers.FeedInformation
+import com.geekorum.ttrss.manage_feeds.databinding.ActivityAddFeedBinding
+import dagger.android.AndroidInjection
+import kotlinx.coroutines.delay
+import okhttp3.HttpUrl
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+/**
+ * Chrome share offline page as multipart/related content send in EXTRA_STREAM
+ * Mime multipart can be parsed with mime4j
+ * 'org.apache.james:apache-mime4j-core:0.8.1'
+ * 'org.apache.james:apache-mime4j-dome:0.8.1'
+ * who doesn't have much dependencies and are used by k9mail
+ */
+class AddFeedActivity : BottomSheetDialogActivity() {
+
+ @Inject
+ internal lateinit var viewModelFactory: DaggerDelegateViewModelsFactory
+
+ private lateinit var feedAdapter: FeedAdapter
+ private lateinit var accountsAdapter: AccountsAdapter
+ private val viewModel: AddFeedViewModel by viewModels { viewModelFactory }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ AndroidInjection.inject(this)
+ super.onCreate(savedInstanceState)
+
+ val urlString = intent.data?.toString() ?: intent.extras?.getString(Intent.EXTRA_TEXT) ?: ""
+
+ val url = HttpUrl.parse(urlString)
+ viewModel.init(url)
+
+ val binding = ActivityAddFeedBinding.inflate(layoutInflater, null, false)
+ binding.viewModel = viewModel
+ binding.lifecycleOwner = this
+ setContentView(binding.root)
+
+ feedAdapter = FeedAdapter(this)
+ binding.availableFeeds.adapter = feedAdapter
+
+ accountsAdapter = AccountsAdapter(this)
+ binding.availableAccounts.adapter = accountsAdapter
+
+ viewModel.accounts.observe(this, Observer {
+ val accounts = checkNotNull(it)
+ with(accountsAdapter) {
+ clear()
+ addAll(*accounts)
+ }
+ })
+
+ viewModel.availableFeeds.observe(this, Observer {
+ val feeds = checkNotNull(it)
+ val text = feeds.firstOrNull()?.title ?: getString(R.string.activity_add_feed_no_feeds_available)
+ binding.availableFeedsSingle.text = text
+ binding.loadingProgress.hide()
+ with(feedAdapter) {
+ clear()
+ addAll(feeds)
+ }
+
+ if (feeds.isEmpty()) {
+ lifecycleScope.launchWhenStarted {
+ delay(TimeUnit.MILLISECONDS.toMillis(1500))
+ finish()
+ }
+ }
+ })
+
+ viewModel.complete.observe(this, EventObserver {
+ finish()
+ })
+ }
+
+}
+
+private class FeedAdapter(context: Context) :
+ ArrayAdapter<FeedInformation>(context, android.R.layout.simple_dropdown_item_1line, mutableListOf()) {
+ init {
+ setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ }
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val v = super.getView(position, convertView, parent) as TextView
+ val item = checkNotNull(getItem(position))
+ v.text = item.title
+ return v
+ }
+
+ override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val v = super.getDropDownView(position, convertView, parent) as TextView
+ val item = checkNotNull(getItem(position))
+ v.text = item.title
+ return v
+ }
+}
+
+
+private class AccountsAdapter(context: Context) : ArrayAdapter<Account>(context, R.layout.item_choose_account, R.id.account_row_text, mutableListOf()) {
+ init {
+ setDropDownViewResource(R.layout.item_choose_account)
+ }
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val v = super.getView(position, convertView, parent)
+ val imageView = v.findViewById<ImageView>(R.id.account_row_icon)
+ imageView.setImageResource(R.mipmap.ic_launcher)
+ val textView = v.findViewById<TextView>(R.id.account_row_text)
+ val item = checkNotNull(getItem(position))
+ textView.text = item.name
+ return v
+ }
+
+ override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val v = super.getDropDownView(position, convertView, parent)
+ val imageView = v.findViewById<ImageView>(R.id.account_row_icon)
+ imageView.setImageResource(R.mipmap.ic_launcher)
+ val textView = v.findViewById<TextView>(R.id.account_row_text)
+ val item = checkNotNull(getItem(position))
+ textView.text = item.name
+ return v
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/manage_feeds/src/main/res/layout/activity_add_feed.xml Wed Jun 26 15:35:06 2019 -0700
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Geekttrss is a RSS feed reader application on the Android Platform.
+
+ Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+
+ This file is part of Geekttrss.
+
+ Geekttrss is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Geekttrss is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Geekttrss. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <data>
+ <import type="android.view.View" />
+ <variable name="viewModel" type="com.geekorum.ttrss.manage_feeds.add_feed.AddFeedViewModel" />
+ </data>
+
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="96dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:layout_marginBottom="16dp"
+ android:gravity="center_vertical"
+ android:textAppearance="@style/TextAppearance.AppCompat.Display1"
+ android:textColor="?android:textColorPrimary"
+ android:textSize="28sp"
+ android:text="@string/activity_add_feed_title"
+ android:background="@color/primary"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark"
+ />
+
+
+ <Spinner android:id="@+id/available_feeds"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/listPreferredItemHeightSmall"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:visibility="@{viewModel.availableFeeds.size() > 1 ? View.VISIBLE : View.INVISIBLE }"
+ android:onItemSelected="@{(spinner, view, position, id) -> viewModel.setSelectedFeed(spinner.getItemAtPosition(position))}"
+ />
+
+
+ <androidx.core.widget.ContentLoadingProgressBar
+ android:id="@+id/loading_progress"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="?android:listPreferredItemPaddingStart"
+ android:layout_marginEnd="?android:listPreferredItemPaddingStart"
+ android:layout_marginHorizontal="?android:listPreferredItemPaddingStart"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="@+id/available_feeds_single"
+ app:layout_constraintBottom_toBottomOf="@+id/available_feeds_single"
+ style="?android:progressBarStyleSmall"
+ tools:ignore="UnusedAttribute" />
+
+
+ <TextView android:id="@+id/available_feeds_single"
+ android:layout_width="0dp"
+ android:layout_height="?attr/listPreferredItemHeightSmall"
+ app:layout_constraintStart_toEndOf="@+id/loading_progress"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title"
+ android:paddingStart="?android:listPreferredItemPaddingStart"
+ android:paddingEnd="?android:listPreferredItemPaddingStart"
+ android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
+ android:gravity="center_vertical"
+ android:visibility="@{viewModel.availableFeeds.size() > 1 ? View.INVISIBLE : View.VISIBLE }"
+ android:text="@string/activity_add_feed_looking_for_feed"
+ />
+
+
+
+ <androidx.constraintlayout.widget.Group
+ android:layout_width="wrap_content" android:layout_height="wrap_content"
+ app:constraint_referenced_ids="accounts_panel,accounts_label,available_accounts"
+ android:visibility="@{viewModel.accounts.length > 1 ? View.VISIBLE : View.GONE }"
+ />
+
+
+ <!-- Background of the accounts panel -->
+ <View android:id="@+id/accounts_panel"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/available_feeds"
+ app:layout_constraintBottom_toTopOf="@id/button_bar"
+ android:layout_marginTop="?listPreferredItemHeightSmall"
+ android:background="@color/background_add_feed_accounts_panel"/>
+
+ <TextView android:id="@+id/accounts_label"
+ android:layout_width="match_parent"
+ android:layout_height="24dp"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@id/accounts_panel"
+ android:gravity="center_vertical"
+ android:textAppearance="?textAppearanceCaption"
+ android:textColor="?android:textColorPrimary"
+ android:text="@string/activity_add_feed_account_subtitle"
+ />
+
+ <Spinner android:id="@+id/available_accounts"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/listPreferredItemHeightSmall"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/accounts_label"
+ android:paddingEnd="8dp"
+ android:paddingStart="8dp"
+ android:onItemSelected="@{(spinner, view, position, id) -> viewModel.setSelectedAccount(spinner.getItemAtPosition(position))}"
+ android:theme="@style/ThemeOverlay.AppTheme.AddFeedActivity.AccountsPanel"
+ />
+
+ <LinearLayout
+ android:id="@+id/button_bar"
+ style="?attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/available_accounts"
+ android:gravity="end|center_vertical"
+ android:orientation="horizontal"
+ android:measureWithLargestChild="true"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:elevation="8dp">
+
+ <Button
+ android:id="@+id/cancel"
+ android:layout_width="wrap_content"
+ android:layout_gravity="start"
+ android:maxLines="2"
+ style="?attr/buttonBarNegativeButtonStyle"
+ android:minHeight="48dp"
+ android:layout_height="wrap_content"
+ android:text="@android:string/cancel"
+ android:onClick="@{() -> viewModel.cancel()}"
+ />
+
+ <Button
+ android:id="@+id/subscribe"
+ android:layout_width="wrap_content"
+ android:layout_gravity="end"
+ android:maxLines="2"
+ android:minHeight="48dp"
+ style="?attr/buttonBarPositiveButtonStyle"
+ android:layout_height="wrap_content"
+ android:text="@string/activity_add_feed_btn_subscribe"
+ android:onClick="@{() -> viewModel.subscribeToFeed()}"
+ android:enabled="@{viewModel.canSubscribe}"
+ />
+ </LinearLayout>
+
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</layout>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/manage_feeds/src/main/res/values-night/colors.xml Wed Jun 26 15:35:06 2019 -0700
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+
+ Geekttrss is a RSS feed reader application on the Android Platform.
+
+ Copyright (C) 2017-2019 by Frederic-Charles Barthelery.
+
+ This file is part of Geekttrss.
+
+ Geekttrss is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Geekttrss is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Geekttrss. If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<resources>
+ <!-- Add feed account panel -->
+ <color name="background_add_feed_accounts_panel">@color/material_blue_grey_900</color>
+ <color name="background_add_feed_accounts_spinner">@color/material_blue_grey_800</color>
+</resources>
--- a/manage_feeds/src/main/res/values/colors.xml Wed Jun 26 15:11:28 2019 -0700
+++ b/manage_feeds/src/main/res/values/colors.xml Wed Jun 26 15:35:06 2019 -0700
@@ -23,4 +23,9 @@
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<color name="rss_feed_orange">#FF9800</color>
+
+ <!-- Add feed account panel -->
+ <color name="background_add_feed_accounts_panel">@color/material_blue_grey_100</color>
+ <color name="background_add_feed_accounts_spinner">@color/material_blue_grey_200</color>
+
</resources>