First use login flow (#44)

* transform profile activity into fragment

* Implement swipeable tabs

* Ask for login on first start, add API endpoints, change profile to show the user's profile

* Add tests

* delete test for now

* Adapt test to changes (no more profile from drawer)

* Add unit test for api

* Add test for profile, refactor to allow testing, add exception to security policy to allow tests

* Adapt test to new situation

* Fix typo due to change

* refactor somewhat

* remove unused function

* remove test dependent on network

* update test

* remove test

Co-authored-by: Ulysse Widmer <ulysse.widmer@epfl.ch>
This commit is contained in:
Wv5twkFEKh54vo4tta9yu7dHa3 2020-03-13 11:48:45 +01:00 committed by GitHub
parent 2d7020fd21
commit 20c5ff4ee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 289 additions and 343 deletions

View File

@ -80,6 +80,7 @@ dependencies {
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
testImplementation 'junit:junit:4.13'
androidTestImplementation("com.squareup.okhttp3:mockwebserver:4.4.0")
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'

View File

@ -1,25 +0,0 @@
package com.h.pixeldroid
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.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BottomMenuTest {
@get:Rule
var activityRule: ActivityScenarioRule<MainActivity>
= ActivityScenarioRule(MainActivity::class.java)
@Test
fun testFollowersTextView() {
onView(withId(R.id.activity_main_account_btn)).perform(click())
onView(withId(R.id.profile_main_container)).check(matches(isDisplayed()))
}
}

View File

@ -59,27 +59,27 @@ class LoginInstrumentedTest {
}
}
@RunWith(AndroidJUnit4::class)
class LoginCheckIntent {
@get:Rule
val intentsTestRule = IntentsTestRule(LoginActivity::class.java)
@Test
fun launchesIntent() {
val expectedIntent: Matcher<Intent> = allOf(
hasAction(ACTION_VIEW),
hasDataString(containsString("pixelfed.social"))
)
onView(withId(R.id.editText)).perform(ViewActions.replaceText("pixelfed.social"), ViewActions.closeSoftKeyboard())
onView(withId(R.id.connect_instance_button)).perform(click())
Thread.sleep(5000)
intended(expectedIntent)
}
}
//@RunWith(AndroidJUnit4::class)
//class LoginCheckIntent {
// @get:Rule
// val intentsTestRule = IntentsTestRule(LoginActivity::class.java)
//
// @Test
// fun launchesIntent() {
// val expectedIntent: Matcher<Intent> = allOf(
// hasAction(ACTION_VIEW),
// hasDataString(containsString("pixelfed.social"))
// )
//
// onView(withId(R.id.editText)).perform(ViewActions.replaceText("pixelfed.social"), ViewActions.closeSoftKeyboard())
// onView(withId(R.id.connect_instance_button)).perform(click())
//
// Thread.sleep(5000)
//
// intended(expectedIntent)
//
// }
//}
@RunWith(AndroidJUnit4::class)
class AfterIntent {

View File

@ -1,13 +1,22 @@
package com.h.pixeldroid
import android.content.Context
import androidx.core.view.get
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.Matchers.not
import androidx.test.platform.app.InstrumentationRegistry
import com.h.pixeldroid.objects.Account
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -16,18 +25,55 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ProfileTest {
private val accountJson = "{\n" +
" \"id\": \"1450\",\n" +
" \"username\": \"deerbard_photo\",\n" +
" \"acct\": \"deerbard_photo\",\n" +
" \"display_name\": \"deerbard photography\",\n" +
" \"locked\": false,\n" +
" \"created_at\": \"2018-08-01T12:58:21.000000Z\",\n" +
" \"followers_count\": 68,\n" +
" \"following_count\": 27,\n" +
" \"statuses_count\": 72,\n" +
" \"note\": \"\",\n" +
" \"url\": \"https://pixelfed.social/deerbard_photo\",\n" +
" \"avatar\": \"https://pixelfed.social/storage/avatars/000/000/001/450/SMSep5NoabDam1W8UDMh_avatar.png?v=4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a\",\n" +
" \"avatar_static\": \"https://pixelfed.social/storage/avatars/000/000/001/450/SMSep5NoabDam1W8UDMh_avatar.png?v=4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a\",\n" +
" \"header\": \"\",\n" +
" \"header_static\": \"\",\n" +
" \"emojis\": [],\n" +
" \"moved\": null,\n" +
" \"fields\": null,\n" +
" \"bot\": false,\n" +
" \"software\": \"pixelfed\",\n" +
" \"is_admin\": false\n" +
" }"
@get:Rule
var activityRule: ActivityScenarioRule<MainActivity>
= ActivityScenarioRule(MainActivity::class.java)
@Before
fun openProfileFragment() {
onView(withId(R.id.activity_main_account_btn)).perform(click())
}
@Before
fun before(){
val server = MockWebServer()
server.enqueue(MockResponse().addHeader("Content-Type", "application/json; charset=utf-8").setBody(accountJson))
server.start()
val baseUrl = server.url("")
val preferences = InstrumentationRegistry.getInstrumentation()
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
preferences.edit().putString("accessToken", "azerty").apply()
preferences.edit().putString("domain", baseUrl.toString()).apply()
ActivityScenario.launch(MainActivity::class.java)
}
@Test
fun randomUsernameCorrectlyLoadedTest() {
Thread.sleep(5000) // wait for the username to load (to modify once we know how)
onView(withId(R.id.accountName)).check(matches(not(withText(R.string.no_username))))
fun testFollowersTextView() {
onView(withId(R.id.view_pager)).perform(ViewActions.swipeLeft()).perform(ViewActions.swipeLeft()).perform(
ViewActions.swipeLeft()
).perform(ViewActions.swipeLeft())
Thread.sleep(1000)
onView(withId(R.id.followers)).check(matches(withText("Followers")))
onView(withId(R.id.accountName)).check(matches(withText("deerbard_photo")))
}
}

View File

@ -1,17 +1,22 @@
package com.h.pixeldroid
import android.content.Context
import android.view.Gravity
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.DrawerActions
import androidx.test.espresso.contrib.DrawerMatchers.isClosed
import androidx.test.espresso.contrib.NavigationViewActions
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.concurrent.thread
@RunWith(AndroidJUnit4::class)
@ -21,24 +26,27 @@ class SettingsTest {
= ActivityScenarioRule(MainActivity::class.java)
@Before
fun openDrawer() {
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
fun before(){
val preferences = InstrumentationRegistry.getInstrumentation()
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
preferences.edit().putString("accessToken", "azerty").apply()
preferences.edit().putString("domain", "http://localhost").apply()
ActivityScenario.launch(MainActivity::class.java)
}
@Test
fun myProfileButtonTest() {
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
onView(withId(R.id.profile_main_container)).check(matches(isDisplayed()))
}
fun testDrawerSettingsButton() {
// Open Drawer to click on navigation.
onView(withId(R.id.drawer_layout))
.check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
.perform(DrawerActions.open()); // Open Drawer
@Test
fun settingsButtonTest() {
// Start the screen of your activity.
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_settings))
onView(withId(R.id.settings)).check(matches(isDisplayed()))
// Check that settings activity was opened.
onView(withText(R.string.signature_title)).check(matches(isDisplayed()))
}
@Test
fun accessibilityButtonTest() {
// TODO if some accessibility view is added
}
}

View File

@ -1,13 +1,18 @@
package com.h.pixeldroid
import android.content.Context
import android.content.Intent
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.swipeLeft
import androidx.test.espresso.action.ViewActions.swipeRight
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.rule.ActivityTestRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@ -17,17 +22,19 @@ class SwipeTest {
@get:Rule
var activityRule: ActivityScenarioRule<MainActivity>
= ActivityScenarioRule(MainActivity::class.java)
@Before
fun before(){
val preferences = getInstrumentation()
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
preferences.edit().putString("accessToken", "azerty").apply()
preferences.edit().putString("domain", "http://localhost").apply()
ActivityScenario.launch(MainActivity::class.java)
}
@Test
fun swipingRightOnHomepageShowsSettings() {
onView(withId(R.id.main_linear_layout)).perform(swipeRight())
onView(withId(R.id.nav_view)).check(matches(isDisplayed()))
}
onView(withId(R.id.view_pager)).perform(swipeLeft()).perform(swipeLeft()).perform(swipeLeft()).perform(swipeLeft())
onView(withId(R.id.nbFollowers)).check(matches(isDisplayed()))
@Test
fun swipeLeftOnHomePageShowsProfile() {
// to modify when we add more tabs
onView(withId(R.id.main_linear_layout)).perform(swipeLeft())
onView(withId(R.id.profile_main_container)).check(matches(isDisplayed()))
}
}

View File

@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"

View File

@ -49,6 +49,9 @@ class LoginActivity : AppCompatActivity() {
}
override fun onBackPressed() {
}
private fun onClickConnect() {
connect_instance_button.isEnabled = false
@ -62,9 +65,9 @@ class LoginActivity : AppCompatActivity() {
}
preferences.edit()
.putString("domain", normalizedDomain)
.putString("domain", "https://$normalizedDomain")
.apply()
registerAppToServer(normalizedDomain)
registerAppToServer("https://$normalizedDomain")
}
@ -97,14 +100,14 @@ class LoginActivity : AppCompatActivity() {
return failedRegistration()
}
}
PixelfedAPI.create("https://$normalizedDomain").registerApplication(
PixelfedAPI.create(normalizedDomain).registerApplication(
APP_NAME,"$OAUTH_SCHEME://$PACKAGE_ID", SCOPE
).enqueue(callback)
}
private fun promptOAuth(normalizedDomain: String, client_id: String) {
val url = "https://$normalizedDomain/oauth/authorize?" +
val url = "$normalizedDomain/oauth/authorize?" +
"client_id" + "=" + client_id + "&" +
"redirect_uri" + "=" + "$OAUTH_SCHEME://$PACKAGE_ID" + "&" +
"response_type=code" + "&" +
@ -150,7 +153,7 @@ class LoginActivity : AppCompatActivity() {
}
}
PixelfedAPI.create("https://$domain")
PixelfedAPI.create("$domain")
.obtainToken(
clientId, clientSecret, "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, code,
"authorization_code"

View File

@ -1,40 +1,43 @@
package com.h.pixeldroid
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.MenuItem
import android.widget.Button
import android.widget.ImageButton
import android.widget.LinearLayout
import androidx.annotation.NonNull
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.navigation.NavigationView
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.h.pixeldroid.fragments.HomeFragment
import com.h.pixeldroid.fragments.ProfileFragment
import com.h.pixeldroid.motions.OnSwipeListener
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
private lateinit var drawerLayout: DrawerLayout
private val newPostsActivityRequestCode = Activity.RESULT_OK
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var preferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mainLinearLayout : LinearLayout = findViewById(R.id.main_linear_layout)
val homepageButton : ImageButton = findViewById(R.id.activity_main_home_btn)
val accountButton : ImageButton = findViewById(R.id.activity_main_account_btn)
preferences = getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
homepageButton.setOnClickListener {
launchFragment(HomeFragment())
}
accountButton.setOnClickListener {
launchFragment(ProfileFragment())
//Check if we have logged in and gotten an access token
if(!preferences.contains("accessToken")){
launchActivity(LoginActivity())
}
// Setup the drawer
@ -42,33 +45,31 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
val navigationView: NavigationView = findViewById(R.id.nav_view)
navigationView.setNavigationItemSelectedListener(this)
val onSwipeListener = object: OnSwipeListener(this) {
override fun onSwipeRight() = swipeRight()
override fun onSwipeLeft() = swipeLeft()
val tabs = arrayOf(HomeFragment(), Fragment(), Fragment(), Fragment(), ProfileFragment())
setupTabs(tabs)
}
private fun setupTabs(tabs: Array<Fragment>){
viewPager = findViewById(R.id.view_pager)
viewPager.adapter = object : FragmentStateAdapter(this) {
override fun createFragment(position: Int): Fragment {
return tabs[position]
}
override fun getItemCount(): Int {
return 5
}
}
mainLinearLayout.setOnTouchListener(onSwipeListener)
// default fragment that displays when we open the app
launchFragment(HomeFragment())
}
private fun swipeRight() {
// TODO: correctly switch between tabs
drawerLayout.openDrawer(GravityCompat.START)
}
private fun swipeLeft() {
// TODO: correctly switch between tabs
supportFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_to_left)
.replace(R.id.fragment_container, ProfileFragment()).commit()
}
/*
Launches the given fragment and put it as the current "activity"
*/
private fun launchFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(R.id.fragment_container, fragment).commit()
tabLayout = findViewById(R.id.tabs)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
when(position){
0 -> tab.icon = getDrawable(R.drawable.ic_home_white_24dp)
1 -> tab.icon = getDrawable(R.drawable.ic_search_white_24dp)
2 -> tab.icon = getDrawable(R.drawable.ic_photo_camera_white_24dp)
3 -> tab.icon = getDrawable(R.drawable.ic_star_white_24dp)
4 -> tab.icon = getDrawable(R.drawable.ic_person_white_24dp)
}
}.attach()
}
/**
@ -77,7 +78,6 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
override fun onNavigationItemSelected(@NonNull item: MenuItem): Boolean {
when (item.itemId){
R.id.nav_settings -> launchActivity(SettingsActivity())
R.id.nav_account -> launchFragment(ProfileFragment())
}
drawerLayout.closeDrawer(GravityCompat.START)

View File

@ -1,5 +1,6 @@
package com.h.pixeldroid.api
import com.h.pixeldroid.objects.Account
import com.h.pixeldroid.objects.Application
import com.h.pixeldroid.objects.Status
import com.h.pixeldroid.objects.Token
@ -7,11 +8,7 @@ import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Query
import retrofit2.http.Field
import retrofit2.http.*
/*
@ -53,6 +50,23 @@ interface PixelfedAPI {
): Call<List<Status>>
@GET("/api/v1/timelines/home")
fun timelineHome(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String,
@Query("max_id") max_id: String? = null,
@Query("since_id") since_id: String? = null,
@Query("min_id") min_id: String? = null,
@Query("limit") limit: Int? = null,
@Query("local") local: Boolean? = null
): Call<List<Status>>
@GET("/api/v1/accounts/verify_credentials")
fun verifyCredentials(
//The authorization header needs to be of the form "Bearer <token>"
@Header("Authorization") authorization: String
): Call<Account>
companion object {
fun create(baseUrl: String): PixelfedAPI {
return Retrofit.Builder()

View File

@ -1,14 +1,10 @@
package com.h.pixeldroid.fragments
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import com.h.pixeldroid.LoginActivity
import androidx.fragment.app.Fragment
import com.h.pixeldroid.R
@ -19,11 +15,6 @@ class HomeFragment : Fragment() {
): View? {
val view: View = inflater.inflate(R.layout.fragment_home, container, false)
val loginButton: Button = view.findViewById(R.id.button_start_login)
loginButton.setOnClickListener {
startActivity(Intent(view.context, LoginActivity::class.java))
}
return view
}
}

View File

@ -1,6 +1,8 @@
package com.h.pixeldroid.fragments
import android.content.Intent
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Typeface
import android.os.Bundle
import android.util.Log
@ -8,12 +10,10 @@ import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import com.bumptech.glide.Glide
import com.h.pixeldroid.LoginActivity
import com.h.pixeldroid.BuildConfig
import com.h.pixeldroid.R
import com.h.pixeldroid.api.PixelfedAPI
import com.h.pixeldroid.objects.Account
@ -22,47 +22,47 @@ import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
const val BASE_URL = "https://pixelfed.de/"
class ProfileFragment : Fragment() {
private lateinit var preferences: SharedPreferences
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_profile, container, false)
preferences = this.activity!!.getSharedPreferences(
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
)
return inflater.inflate(R.layout.fragment_profile, container, false)
}
var statuses: ArrayList<Status>?
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val pixelfedAPI = PixelfedAPI.create(BASE_URL)
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
val accessToken = preferences.getString("accessToken", "")
pixelfedAPI.timelinePublic(null, null, null, null, null)
.enqueue(object : Callback<List<Status>> {
override fun onResponse(call: Call<List<Status>>, response: Response<List<Status>>) {
pixelfedAPI.verifyCredentials("Bearer $accessToken")
.enqueue(object : Callback<Account> {
override fun onResponse(call: Call<Account>, response: Response<Account>) {
if (response.code() == 200) {
statuses = response.body() as ArrayList<Status>?
val account = response.body()!!
if(!statuses.isNullOrEmpty()) {
setContent(view, account)
val account = statuses!![0].account
setContent(view, account)
}
}
}
override fun onFailure(call: Call<List<Status>>, t: Throwable) {
Log.e("Ouch, not OK", t.toString())
override fun onFailure(call: Call<Account>, t: Throwable) {
Log.e("ProfileFragment:", t.toString())
}
})
return view
}
private fun setContent(view: View, account: Account) {
// ImageView : profile picture
val profilePicture = view.findViewById<ImageView>(R.id.profilePicture)
Glide.with(view.context.applicationContext).load(account.avatar).into(profilePicture)
Glide.with(view.context).load(account.avatar).into(profilePicture)
// TextView : description / bio
val description = view.findViewById<TextView>(R.id.description)

View File

@ -1,54 +0,0 @@
package com.h.pixeldroid.motions
import android.content.Context
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import kotlin.math.abs
const val SWIPE_DISTANCE_THRESHOLD: Int = 100
const val SWIPE_VELOCITY_THRESHOLD: Int = 100
/**
* Detects left and right swipes across a view.
*
* inspired from https://stackoverflow.com/questions/4139288/android-how-to-handle-right-to-left-swipe-gestures
*/
open class OnSwipeListener(context: Context?) : OnTouchListener {
private val gestureDetector: GestureDetector = GestureDetector(context, GestureListener())
override fun onTouch(v: View?, event: MotionEvent?): Boolean =
gestureDetector.onTouchEvent(event)
// redefining gesture listener to call our custom functions
private inner class GestureListener : SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean = true
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val distanceX = e2.x - e1.x
val distanceY = e2.y - e1.y
if (abs(distanceX) > abs(distanceY) // swipe on the side and not up or down
&& abs(distanceX) > SWIPE_DISTANCE_THRESHOLD
&& abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
if (distanceX > 0) onSwipeRight() else onSwipeLeft()
return true
}
return false
}
}
open fun onSwipeLeft() {}
open fun onSwipeRight() {}
}

View File

@ -12,69 +12,26 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/main_linear_layout">
android:orientation="vertical">
<FrameLayout
android:id="@+id/fragment_container"
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
android:orientation="horizontal">
<ImageButton
android:id="@+id/activity_main_home_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:padding="10dp"
app:srcCompat="@drawable/ic_home_white_24dp" />
<ImageButton
android:id="@+id/activity_main_search_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="10dp"
android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_search_white_24dp" />
<ImageButton
android:id="@+id/activity_main_camera_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="10dp"
android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_photo_camera_white_24dp" />
<ImageButton
android:id="@+id/activity_main_favorite_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:padding="10dp"
android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_star_white_24dp" />
<ImageButton
android:id="@+id/activity_main_account_btn"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
app:srcCompat="@drawable/ic_person_white_24dp" />
</LinearLayout>
app:tabMode="fixed"
app:tabBackground="@color/colorPrimary">
</com.google.android.material.tabs.TabLayout>
</LinearLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"

View File

@ -9,15 +9,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<Button
android:id="@+id/button_start_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/start_login" />
</LinearLayout>
android:gravity="center"/>
</FrameLayout>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>

File diff suppressed because one or more lines are too long

View File

@ -1,20 +0,0 @@
package com.h.pixeldroid
import com.h.pixeldroid.motions.OnSwipeListener
import org.junit.Assert.assertEquals
import org.junit.Test
class OnSwipeListenerUnitTest {
@Test
fun undefinedSwipeRightDoesNothingTest() {
val swipeListener = OnSwipeListener(null)
assertEquals(Unit, swipeListener.onSwipeRight())
}
@Test
fun undefinedSwipeLeftDoesNothingTest() {
val swipeListener = OnSwipeListener(null)
assertEquals(Unit, swipeListener.onSwipeLeft())
}
}