Manage_feeds: add AddFeedActivity
authorDa Risk <da_risk@geekorum.com>
Wed, 26 Jun 2019 15:35:06 -0700
changeset 314 b616d036a865
parent 313 bc2432eeeeec
child 315 d54d670a691a
Manage_feeds: add AddFeedActivity
manage_feeds/build.gradle.kts
manage_feeds/src/androidTest/java/com/geekorum/ttrss/manage_feeds/add_feed/AddFeedActivityTest.kt
manage_feeds/src/main/java/com/geekorum/ttrss/manage_feeds/add_feed/AddFeedActivity.kt
manage_feeds/src/main/res/layout/activity_add_feed.xml
manage_feeds/src/main/res/values-night/colors.xml
manage_feeds/src/main/res/values/colors.xml
--- 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() &gt; 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() &gt; 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 &gt; 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>