Offline mode - Phase 1 (#156)
* rework post entity * refactor login activity * added db instance to login activity * remember user logins offline * drawer works offline * fixed some tests * move imagefragment in fragment folder * added tests for the new login flow * add missing drawer test * add login offline test * added online login flow tests * fixed tests * added mockserver /instance repsonse * fixed marie's request
This commit is contained in:
parent
b2842b8abe
commit
e96d5e22a7
@ -70,12 +70,14 @@ dependencies {
|
||||
|
||||
def room_version = "2.2.5"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
testImplementation "androidx.room:room-testing:$room_version"
|
||||
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
||||
implementation 'info.androidhive:imagefilters:1.0.7'
|
||||
|
||||
@ -111,7 +113,10 @@ dependencies {
|
||||
def fragment_version = '1.2.4'
|
||||
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
|
||||
|
||||
implementation 'com.karumi:dexter:6.1.0'
|
||||
implementation 'com.karumi:dexter:6.1.2'
|
||||
|
||||
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
|
||||
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
|
@ -1,70 +0,0 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.PostDao
|
||||
import com.h.pixeldroid.db.PostEntity
|
||||
import com.h.pixeldroid.utils.*
|
||||
import org.junit.*
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
import java.util.Calendar
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AppDatabaseTest {
|
||||
private var postDao: PostDao? = null
|
||||
private var db: AppDatabase? = null
|
||||
private var postTest = PostEntity(1, "test", date= Calendar.getInstance().time)
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@Before
|
||||
fun setup() {
|
||||
AppDatabase.TEST_MODE = true
|
||||
db = AppDatabase.getDatabase(ApplicationProvider.getApplicationContext())
|
||||
postDao = db?.postDao()
|
||||
postDao?.insertAll(postTest)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInsertPostItem() {
|
||||
Assert.assertEquals(postTest.domain, postDao?.getById(postTest.uid)!!.domain)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDeleteAll(){
|
||||
postDao?.deleteAll()
|
||||
Assert.assertEquals(postDao?.getPostsCount(), 0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUtilsInsertAll() {
|
||||
val postTest2 = PostEntity(2, "test", date= Calendar.getInstance().time)
|
||||
DatabaseUtils.insertAllPosts(db!!, postTest, postTest2)
|
||||
|
||||
Assert.assertEquals(postTest.domain, postDao?.getById(postTest.uid)!!.domain)
|
||||
Assert.assertEquals(postTest2.domain, postDao?.getById(postTest2.uid)!!.domain)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUtilsLRU() {
|
||||
for(i in 1..db!!.MAX_NUMBER_OF_POSTS) {
|
||||
//sleep a bit to not have the weird concurrency bugs?
|
||||
Thread.sleep(10)
|
||||
DatabaseUtils.insertAllPosts(db!!, PostEntity(i, i.toString(), date= Calendar.getInstance().time))
|
||||
}
|
||||
|
||||
Assert.assertEquals("1", postDao?.getById(1)!!.domain)
|
||||
Assert.assertEquals(db?.MAX_NUMBER_OF_POSTS, postDao?.getPostsCount())
|
||||
|
||||
DatabaseUtils.insertAllPosts(db!!, PostEntity(0, "0", date= Calendar.getInstance().time))
|
||||
Assert.assertEquals(db?.MAX_NUMBER_OF_POSTS, postDao?.getPostsCount())
|
||||
val eldestPost = postDao?.getById(1)
|
||||
Assert.assertEquals(null, eldestPost)
|
||||
}
|
||||
}
|
@ -13,6 +13,8 @@ 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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@ -42,7 +44,7 @@ class DrawerMenuTest {
|
||||
// Open Drawer to click on navigation.
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
onView(withId(R.id.drawer_layout))
|
||||
.check(matches(DrawerMatchers.isClosed(Gravity.LEFT))) // Left Drawer should be closed.
|
||||
.check(matches(DrawerMatchers.isClosed())) // Left Drawer should be closed.
|
||||
.perform(DrawerActions.open()) // Open Drawer
|
||||
}
|
||||
|
||||
@ -116,4 +118,11 @@ class DrawerMenuTest {
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("Andrew Dobis")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onBackPressedClosesDrawer() {
|
||||
UiDevice.getInstance(getInstrumentation()).pressBack()
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.drawer_layout)).check(matches(DrawerMatchers.isClosed()))
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import android.content.Intent
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.net.Uri
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
@ -16,12 +17,19 @@ import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasDataString
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasErrorText
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
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 androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiSelector
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.CoreMatchers.anyOf
|
||||
import org.hamcrest.CoreMatchers.containsString
|
||||
@ -39,34 +47,6 @@ import org.junit.runner.RunWith
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginInstrumentedTest {
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@get:Rule
|
||||
var activityRule: ActivityScenarioRule<LoginActivity>
|
||||
= ActivityScenarioRule(LoginActivity::class.java)
|
||||
|
||||
@Test
|
||||
fun clickConnect() {
|
||||
onView(withId(R.id.connect_instance_button)).check(matches(withText("Connect to Pixelfed")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun invalidURL() {
|
||||
onView(withId(R.id.editText)).perform(ViewActions.replaceText("/jdi"), ViewActions.closeSoftKeyboard())
|
||||
onView(withId(R.id.connect_instance_button)).perform(scrollTo()).perform(click())
|
||||
onView(withId(R.id.editText)).check(matches(hasErrorText("Invalid domain")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notPixelfedInstance() {
|
||||
onView(withId(R.id.editText)).perform(ViewActions.replaceText("localhost"), ViewActions.closeSoftKeyboard())
|
||||
onView(withId(R.id.connect_instance_button)).perform(scrollTo()).perform(click())
|
||||
onView(withId(R.id.editText)).check(matches(hasErrorText("Could not register the application with this server")))
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginCheckIntent {
|
||||
@get:Rule
|
||||
@ -117,33 +97,3 @@ class LoginCheckIntent {
|
||||
Intents.release()
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class AfterIntent {
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
|
||||
@get:Rule
|
||||
val rule = ActivityTestRule(LoginActivity::class.java)
|
||||
private var launchedActivity: Activity? = null
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val preferences = InstrumentationRegistry.getInstrumentation()
|
||||
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
preferences.edit().putString("domain", "http://localhost").apply()
|
||||
val intent = Intent(ACTION_VIEW, Uri.parse("oauth2redirect://com.h.pixeldroid?code=sdfdqsf"))
|
||||
launchedActivity = rule.launchActivity(intent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun usesIntent() {
|
||||
|
||||
Thread.sleep(5000)
|
||||
|
||||
onView(withId(R.id.editText)).check(matches(
|
||||
anyOf(hasErrorText("Error getting token"),
|
||||
hasErrorText("Could not authenticate"))))
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
import androidx.test.uiautomator.UiSelector
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_instance_chooser_button
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginActivityOfflineTest {
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var device: UiDevice
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
device = UiDevice.getInstance(getInstrumentation())
|
||||
device.openQuickSettings()
|
||||
device.findObject(UiSelector().textContains("airplane")).click()
|
||||
device.pressHome()
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyDBandOfflineModeDisplayCorrectMessage() {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
onView(withId(R.id.login_activity_connection_required_text)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun offlineModeSelectAvailabeLaunchesMainActivityWithStoredAccountInstance() {
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = "some_uri",
|
||||
title = "PixelTest"
|
||||
))
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "some_user_id",
|
||||
instance_uri = "some_uri",
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url"
|
||||
))
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
onView(withId(R.id.login_activity_instance_chooser_button)).perform(click())
|
||||
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withId(R.id.drawer_account_name)).check(matches(withText("Testi Testo")))
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
device.openQuickSettings()
|
||||
device.findObject(UiSelector().textContains("airplane")).click()
|
||||
device.pressHome()
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
|
||||
import androidx.test.espresso.action.ViewActions.replaceText
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasErrorText
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class LoginActivityOnlineTest {
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
private lateinit var pref: SharedPreferences
|
||||
private lateinit var server: MockServer
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
server = MockServer()
|
||||
server.start()
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
pref = context.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
pref.edit().clear().apply()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun connectToSavedAccount() {
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = "some_uri",
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "some_user_id",
|
||||
instance_uri = "some_uri",
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url"
|
||||
)
|
||||
)
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
onView(withId(R.id.login_activity_instance_chooser_button)).perform(click())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notPixelfedInstance() {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
onView(withId(R.id.editText))
|
||||
.perform(replaceText("localhost"), closeSoftKeyboard())
|
||||
onView(withId(R.id.connect_instance_button)).perform(click())
|
||||
onView(withId(R.id.editText))
|
||||
.check(matches(hasErrorText(context.getString(R.string.registration_failed))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyStringNotAllowed() {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
onView(withId(R.id.connect_instance_button)).perform(click())
|
||||
onView(withId(R.id.editText)).check(matches(
|
||||
hasErrorText(context.getString(R.string.login_empty_string_error))
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wrongIntentReturnInfoFailsTest() {
|
||||
pref.edit()
|
||||
.putString("domain", "https://dhbfnhgbdbbet")
|
||||
.putString("clientID", "iwndoiuqwnd")
|
||||
.putString("clientSecret", "wlifowed")
|
||||
.apply()
|
||||
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=sdfdqsf")
|
||||
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
|
||||
ActivityScenario.launch<LoginActivity>(intent)
|
||||
onView(withId(R.id.editText)).check(matches(
|
||||
hasErrorText(context.getString(R.string.token_error))
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun incompleteIntentReturnInfoFailsTest() {
|
||||
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=")
|
||||
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
|
||||
ActivityScenario.launch<LoginActivity>(intent)
|
||||
onView(withId(R.id.editText)).check(matches(
|
||||
hasErrorText(context.getString(R.string.auth_failed))
|
||||
))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun correctIntentReturnLoadsMainActivity() {
|
||||
pref.edit()
|
||||
.putString("accessToken", "azerty")
|
||||
.putString("domain", server.getUrl().toString())
|
||||
.putString("clientID", "test_id")
|
||||
.putString("clientSecret", "test_secret")
|
||||
.apply()
|
||||
val uri = Uri.parse("oauth2redirect://com.h.pixeldroid?code=test_code")
|
||||
val intent = Intent(ACTION_VIEW, uri, context, LoginActivity::class.java)
|
||||
ActivityScenario.launch<LoginActivity>(intent)
|
||||
onView(withId(R.id.main_activity_main_linear_layout)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -4,6 +4,7 @@
|
||||
package="com.h.pixeldroid">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
@ -8,15 +8,30 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Application
|
||||
import com.h.pixeldroid.objects.Instance
|
||||
import com.h.pixeldroid.objects.Token
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import okhttp3.HttpUrl
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.Utils
|
||||
import kotlinx.android.synthetic.main.activity_login.connect_instance_button
|
||||
import kotlinx.android.synthetic.main.activity_login.editText
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_connection_required_text
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_instance_chooser
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_instance_chooser_button
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_instance_chooser_layout
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_instance_chooser_offline_text
|
||||
import kotlinx.android.synthetic.main.activity_login.login_activity_instance_input_layout
|
||||
import kotlinx.android.synthetic.main.activity_login.progressLayout
|
||||
import kotlinx.android.synthetic.main.activity_login.whatsAnInstanceTextView
|
||||
import okhttp3.internal.toImmutableList
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
@ -24,41 +39,108 @@ import retrofit2.Response
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private val TAG = "Login Activity"
|
||||
companion object {
|
||||
private const val TAG = "Login Activity"
|
||||
private const val PACKAGE_ID = BuildConfig.APPLICATION_ID
|
||||
private const val SCOPE = "read write follow"
|
||||
}
|
||||
|
||||
private lateinit var OAUTH_SCHEME: String
|
||||
private val PACKAGE_ID = BuildConfig.APPLICATION_ID
|
||||
private val SCOPE = "read write follow"
|
||||
private lateinit var APP_NAME: String
|
||||
private lateinit var oauthScheme: String
|
||||
private lateinit var appName: String
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var pixelfedAPI: PixelfedAPI
|
||||
private var inputVisibility: Int = View.GONE
|
||||
private var chooserVisibility: Int = View.GONE
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_login)
|
||||
|
||||
connect_instance_button.setOnClickListener { onClickConnect() }
|
||||
whatsAnInstanceTextView.setOnClickListener{ whatsAnInstance() }
|
||||
loadingAnimation(true)
|
||||
appName = getString(R.string.app_name)
|
||||
oauthScheme = getString(R.string.auth_scheme)
|
||||
preferences = getSharedPreferences("$PACKAGE_ID.pref", Context.MODE_PRIVATE)
|
||||
db = DBUtils.initDB(applicationContext)
|
||||
|
||||
APP_NAME = getString(R.string.app_name)
|
||||
OAUTH_SCHEME = getString(R.string.auth_scheme)
|
||||
preferences = getSharedPreferences(
|
||||
"$PACKAGE_ID.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
// check for stored accounts/instances
|
||||
val accounts: List<Map<String, String>> = getSavedAccounts()
|
||||
if (accounts.isNotEmpty()) {
|
||||
displayChooser(accounts)
|
||||
login_activity_instance_chooser_button.setOnClickListener {
|
||||
val choice: Int = login_activity_instance_chooser.selectedItemId.toInt()
|
||||
setPreferences(accounts[choice])
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
if (Utils.hasInternet(applicationContext)) {
|
||||
connect_instance_button.setOnClickListener {
|
||||
registerAppToServer(normalizeDomain(editText.text.toString()))
|
||||
}
|
||||
whatsAnInstanceTextView.setOnClickListener{ whatsAnInstance() }
|
||||
inputVisibility = View.VISIBLE
|
||||
} else {
|
||||
if (accounts.isEmpty()) {
|
||||
login_activity_connection_required_text.visibility = View.VISIBLE
|
||||
} else {
|
||||
login_activity_instance_chooser_offline_text.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
loadingAnimation(false)
|
||||
}
|
||||
|
||||
private fun getSavedAccounts(): List<Map<String, String>> {
|
||||
val result = mutableListOf<Map<String, String>>()
|
||||
val instances = db.instanceDao().getAll()
|
||||
for (user in db.userDao().getAll()) {
|
||||
val instance = instances.first {instance ->
|
||||
instance.uri == user.instance_uri
|
||||
}
|
||||
result.add(mapOf(
|
||||
Pair("username", user.username),
|
||||
Pair("instance_title", instance.title),
|
||||
Pair("instance_uri", instance.uri),
|
||||
Pair("id", user.user_id)
|
||||
))
|
||||
}
|
||||
return result.toImmutableList()
|
||||
}
|
||||
|
||||
private fun displayChooser(accounts: List<Map<String, String>>) {
|
||||
ArrayAdapter(
|
||||
this,
|
||||
android.R.layout.simple_spinner_item,
|
||||
accounts.map { account ->
|
||||
"${account["username"]}@${account["instance_title"]}"
|
||||
}).also {
|
||||
adapter ->
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
login_activity_instance_chooser.adapter = adapter
|
||||
}
|
||||
chooserVisibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun setPreferences(account: Map<String, String>) {
|
||||
if (Utils.hasInternet(applicationContext))
|
||||
registerAppToServer(normalizeDomain(account["instance_uri"].orEmpty()))
|
||||
else
|
||||
preferences.edit()
|
||||
.putString("user_id", account["id"])
|
||||
.apply()
|
||||
}
|
||||
|
||||
override fun onStart(){
|
||||
super.onStart()
|
||||
|
||||
val url = intent.data
|
||||
val url: Uri? = intent.data
|
||||
|
||||
//Check if the activity was started after the authentication
|
||||
if (url == null || !url.toString().startsWith("$OAUTH_SCHEME://$PACKAGE_ID")) return
|
||||
|
||||
if (url == null || !url.toString().startsWith("$oauthScheme://$PACKAGE_ID")) return
|
||||
loadingAnimation(true)
|
||||
|
||||
val code = url.getQueryParameter("code")
|
||||
authenticate(code)
|
||||
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
@ -69,82 +151,54 @@ class LoginActivity : AppCompatActivity() {
|
||||
override fun onBackPressed() {
|
||||
}
|
||||
|
||||
private fun onClickConnect() {
|
||||
|
||||
val normalizedDomain = normalizeDomain(editText.text.toString())
|
||||
|
||||
try{
|
||||
HttpUrl.Builder().host(normalizedDomain).scheme("https").build()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return failedRegistration(getString(R.string.invalid_domain))
|
||||
}
|
||||
|
||||
hideKeyboard()
|
||||
loadingAnimation(true)
|
||||
|
||||
preferences.edit()
|
||||
.putString("domain", "https://$normalizedDomain")
|
||||
.apply()
|
||||
getInstanceConfig()
|
||||
registerAppToServer("https://$normalizedDomain")
|
||||
|
||||
}
|
||||
|
||||
private fun whatsAnInstance() {
|
||||
val i = Intent(Intent.ACTION_VIEW)
|
||||
i.data = Uri.parse("https://pixelfed.org/join")
|
||||
startActivity(i)
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
val view = currentFocus
|
||||
if (view != null) {
|
||||
(getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(
|
||||
view.windowToken,
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
}
|
||||
private fun normalizeDomain(domain: String): String {
|
||||
return "https://" + domain
|
||||
.replace("http://", "")
|
||||
.replace("https://", "")
|
||||
.trim(Char::isWhitespace)
|
||||
}
|
||||
|
||||
private fun normalizeDomain(domain: String): String {
|
||||
var d = domain.replace("http://", "")
|
||||
d = d.replace("https://", "")
|
||||
return d.trim(Char::isWhitespace)
|
||||
}
|
||||
|
||||
private fun registerAppToServer(normalizedDomain: String) {
|
||||
val callback = object : Callback<Application> {
|
||||
loadingAnimation(true)
|
||||
if (normalizedDomain.replace("https://", "").isNullOrBlank())
|
||||
return failedRegistration(getString(R.string.login_empty_string_error))
|
||||
PixelfedAPI.create(normalizedDomain).registerApplication(
|
||||
appName,"$oauthScheme://$PACKAGE_ID", SCOPE
|
||||
).enqueue(object : Callback<Application> {
|
||||
override fun onResponse(call: Call<Application>, response: Response<Application>) {
|
||||
if (!response.isSuccessful) {
|
||||
return failedRegistration()
|
||||
}
|
||||
|
||||
val credentials = response.body()
|
||||
val clientId = credentials?.client_id ?: return failedRegistration()
|
||||
val clientSecret = credentials.client_secret
|
||||
|
||||
preferences.edit()
|
||||
.putString("domain", normalizedDomain)
|
||||
.apply()
|
||||
val credentials = response.body() as Application
|
||||
val clientId = credentials.client_id ?: return failedRegistration()
|
||||
preferences.edit()
|
||||
.putString("clientID", clientId)
|
||||
.putString("clientSecret", clientSecret)
|
||||
.putString("clientSecret", credentials.client_secret)
|
||||
.apply()
|
||||
|
||||
promptOAuth(normalizedDomain, clientId)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Application>, t: Throwable) {
|
||||
return failedRegistration()
|
||||
}
|
||||
}
|
||||
PixelfedAPI.create(normalizedDomain).registerApplication(
|
||||
APP_NAME,"$OAUTH_SCHEME://$PACKAGE_ID", SCOPE
|
||||
).enqueue(callback)
|
||||
})
|
||||
}
|
||||
|
||||
private fun promptOAuth(normalizedDomain: String, client_id: String) {
|
||||
|
||||
val url = "$normalizedDomain/oauth/authorize?" +
|
||||
"client_id" + "=" + client_id + "&" +
|
||||
"redirect_uri" + "=" + "$OAUTH_SCHEME://$PACKAGE_ID" + "&" +
|
||||
"redirect_uri" + "=" + "$oauthScheme://$PACKAGE_ID" + "&" +
|
||||
"response_type=code" + "&" +
|
||||
"scope=$SCOPE"
|
||||
|
||||
@ -165,11 +219,11 @@ class LoginActivity : AppCompatActivity() {
|
||||
private fun authenticate(code: String?) {
|
||||
|
||||
// Get previous values from preferences
|
||||
val domain = preferences.getString("domain", "")
|
||||
val clientId = preferences.getString("clientID", "")
|
||||
val clientSecret = preferences.getString("clientSecret", "")
|
||||
val domain = preferences.getString("domain", "") as String
|
||||
val clientId = preferences.getString("clientID", "") as String
|
||||
val clientSecret = preferences.getString("clientSecret", "") as String
|
||||
|
||||
if (code == null || domain.isNullOrEmpty() || clientId.isNullOrEmpty() || clientSecret.isNullOrEmpty()) {
|
||||
if (code.isNullOrBlank() || domain.isBlank() || clientId.isBlank() || clientSecret.isBlank()) {
|
||||
return failedRegistration(getString(R.string.auth_failed))
|
||||
}
|
||||
|
||||
@ -187,61 +241,84 @@ class LoginActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
PixelfedAPI.create("$domain")
|
||||
.obtainToken(
|
||||
clientId, clientSecret, "$OAUTH_SCHEME://$PACKAGE_ID", SCOPE, code,
|
||||
pixelfedAPI = PixelfedAPI.create(domain)
|
||||
pixelfedAPI.obtainToken(
|
||||
clientId, clientSecret, "$oauthScheme://$PACKAGE_ID", SCOPE, code,
|
||||
"authorization_code"
|
||||
).enqueue(callback)
|
||||
}
|
||||
|
||||
private fun authenticationSuccessful(accessToken: String) {
|
||||
saveUserAndInstance(accessToken)
|
||||
preferences.edit().putString("accessToken", accessToken).apply()
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun failedRegistration(message: String =
|
||||
getString(R.string.registration_failed)){
|
||||
private fun failedRegistration(message: String = getString(R.string.registration_failed)) {
|
||||
loadingAnimation(false)
|
||||
editText.error = message
|
||||
}
|
||||
|
||||
private fun loadingAnimation(on: Boolean){
|
||||
if(on) {
|
||||
domainTextInputLayout.visibility = View.GONE
|
||||
login_activity_instance_input_layout.visibility = View.GONE
|
||||
login_activity_instance_chooser_layout.visibility = View.GONE
|
||||
progressLayout.visibility = View.VISIBLE
|
||||
}
|
||||
else {
|
||||
domainTextInputLayout.visibility = View.VISIBLE
|
||||
login_activity_instance_input_layout.visibility = inputVisibility
|
||||
login_activity_instance_chooser_layout.visibility = chooserVisibility
|
||||
progressLayout.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInstanceConfig() {
|
||||
// to get max post description length, can be enhanced for other things
|
||||
// see /api/v1/instance
|
||||
PixelfedAPI.create(preferences.getString("domain", "")!!)
|
||||
.instance().enqueue(object : Callback<Instance> {
|
||||
|
||||
override fun onFailure(call: Call<Instance>, t: Throwable) {
|
||||
Log.e(TAG, "Request to fetch instance config failed.")
|
||||
preferences.edit().putInt("max_toot_chars", 500).apply()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Instance>, response: Response<Instance>) {
|
||||
if (response.code() == 200) {
|
||||
preferences.edit().putInt(
|
||||
"max_toot_chars",
|
||||
response.body()!!.max_toot_chars.toInt()
|
||||
).apply()
|
||||
} else {
|
||||
Log.e(TAG, "Server response to fetch instance config failed.")
|
||||
preferences.edit().putInt("max_toot_chars", 500).apply()
|
||||
private fun saveUserAndInstance(accessToken: String) {
|
||||
preferences.edit().putInt("max_toot_chars", Instance.DEFAULT_MAX_TOOT_CHARS).apply()
|
||||
pixelfedAPI.instance().enqueue(object : Callback<Instance> {
|
||||
override fun onFailure(call: Call<Instance>, t: Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
override fun onResponse(call: Call<Instance>, response: Response<Instance>) {
|
||||
if (response.isSuccessful && response.body() != null) {
|
||||
val instance = response.body() as Instance
|
||||
storeInstance(instance)
|
||||
storeUser(accessToken)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun storeInstance(instance: Instance) {
|
||||
val maxTootChars = instance.max_toot_chars.toInt()
|
||||
preferences.edit().putInt("max_toot_chars", maxTootChars).apply()
|
||||
preferences.edit().putString("instance_uri", instance.uri).apply()
|
||||
val dbInstance = InstanceDatabaseEntity(
|
||||
uri = instance.uri,
|
||||
title = instance.title,
|
||||
max_toot_chars = maxTootChars,
|
||||
thumbnail = instance.thumbnail
|
||||
)
|
||||
db.instanceDao().insertInstance(dbInstance)
|
||||
}
|
||||
|
||||
private fun storeUser(accessToken: String) {
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.body() != null && response.isSuccessful) {
|
||||
val user = response.body() as Account
|
||||
preferences.edit().putString("user_id", user.id).apply()
|
||||
DBUtils.addUser(
|
||||
db,
|
||||
user,
|
||||
preferences.getString("instance_uri", null).orEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,38 +7,39 @@ import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
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.api.PixelfedAPI
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.fragments.NewPostFragment
|
||||
import com.h.pixeldroid.fragments.SearchDiscoverFragment
|
||||
import com.h.pixeldroid.fragments.feeds.PostsFeedFragment
|
||||
import com.h.pixeldroid.fragments.feeds.NotificationsFragment
|
||||
import com.h.pixeldroid.fragments.feeds.OfflineFeedFragment
|
||||
import com.h.pixeldroid.fragments.feeds.PublicTimelineFragment
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.Utils.Companion.hasInternet
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.nav_header.view.drawer_account_name
|
||||
import kotlinx.android.synthetic.main.nav_header.view.drawer_avatar
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener {
|
||||
|
||||
private lateinit var drawerLayout: DrawerLayout
|
||||
private lateinit var viewPager: ViewPager2
|
||||
private lateinit var tabLayout: TabLayout
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private val searchDiscoverFragment: SearchDiscoverFragment = SearchDiscoverFragment()
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.AppTheme_NoActionBar)
|
||||
@ -48,75 +49,89 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
preferences = getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
db = DBUtils.initDB(applicationContext)
|
||||
|
||||
//Check if we have logged in and gotten an access token
|
||||
if(!preferences.contains("accessToken")){
|
||||
if((hasInternet(applicationContext) && !preferences.contains("accessToken"))
|
||||
|| (!hasInternet(applicationContext) && !preferences.contains("user_id"))) {
|
||||
launchActivity(LoginActivity())
|
||||
} else {
|
||||
setupDrawer()
|
||||
|
||||
val tabs = arrayOf(
|
||||
PostsFeedFragment(),
|
||||
if (hasInternet(applicationContext)) PostsFeedFragment()
|
||||
else OfflineFeedFragment(),
|
||||
searchDiscoverFragment,
|
||||
NewPostFragment(),
|
||||
NotificationsFragment(),
|
||||
PublicTimelineFragment()
|
||||
)
|
||||
|
||||
setupTabs(tabs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDrawer() {
|
||||
drawerLayout = findViewById(R.id.drawer_layout)
|
||||
val navigationView: NavigationView = findViewById(R.id.nav_view)
|
||||
navigationView.setNavigationItemSelectedListener(this)
|
||||
|
||||
// Setup views
|
||||
val accessToken = preferences.getString("accessToken", "")
|
||||
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
|
||||
val drawerHeader = navigationView.getHeaderView(0)
|
||||
val accountName = drawerHeader.findViewById<TextView>(R.id.drawer_account_name)
|
||||
val avatar = drawerHeader.findViewById<ImageView>(R.id.drawer_avatar)
|
||||
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.code() == 200) {
|
||||
val account = response.body()!!
|
||||
|
||||
// Set profile picture
|
||||
ImageConverter.setRoundImageFromURL(
|
||||
View(applicationContext), account.avatar_static, avatar)
|
||||
avatar.setOnClickListener{ launchActivity(ProfileActivity()) }
|
||||
|
||||
// Set account name
|
||||
accountName.text = account.display_name
|
||||
accountName.setOnClickListener{ launchActivity(ProfileActivity()) }
|
||||
nav_view.setNavigationItemSelectedListener(this)
|
||||
if (hasInternet(applicationContext)) {
|
||||
val accessToken = preferences.getString("accessToken", "")
|
||||
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.body() != null && response.isSuccessful) {
|
||||
val account = response.body() as Account
|
||||
DBUtils.addUser(db, account)
|
||||
fillDrawerAccountInfo(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("DRAWER ACCOUNT:", t.toString())
|
||||
}
|
||||
})
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("DRAWER ACCOUNT:", t.toString())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
val userId = preferences.getString("user_id", null).orEmpty()
|
||||
if (userId.isNotEmpty()) {
|
||||
val user: UserDatabaseEntity = db.userDao().getUserWithId(userId)
|
||||
val account = Account(
|
||||
id = user.user_id,
|
||||
username = user.username,
|
||||
display_name = user.display_name,
|
||||
avatar_static = user.avatar_static
|
||||
)
|
||||
fillDrawerAccountInfo(account)
|
||||
} else {
|
||||
launchActivity(LoginActivity())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabs(tabs: Array<Fragment>){
|
||||
private fun fillDrawerAccountInfo(account: Account) {
|
||||
val drawerAvatar = nav_view.getHeaderView(0).drawer_avatar
|
||||
val drawerAccountName = nav_view.getHeaderView(0).drawer_account_name
|
||||
ImageConverter.setRoundImageFromURL(
|
||||
View(applicationContext),
|
||||
account.avatar_static,
|
||||
drawerAvatar
|
||||
)
|
||||
drawerAvatar.setOnClickListener { launchActivity(ProfileActivity()) }
|
||||
// Set account name
|
||||
drawerAccountName.apply {
|
||||
text = account.display_name
|
||||
setOnClickListener { launchActivity(ProfileActivity()) }
|
||||
}
|
||||
}
|
||||
|
||||
viewPager = findViewById(R.id.view_pager)
|
||||
viewPager.adapter = object : FragmentStateAdapter(this) {
|
||||
private fun setupTabs(tab_array: Array<Fragment>){
|
||||
view_pager.adapter = object : FragmentStateAdapter(this) {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return tabs[position]
|
||||
return tab_array[position]
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
tabLayout = findViewById(R.id.tabs)
|
||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||
TabLayoutMediator(tabs, view_pager) { tab, position ->
|
||||
when(position){
|
||||
0 -> tab.icon = getDrawable(R.drawable.ic_home_white_24dp)
|
||||
1 -> tab.icon = getDrawable(R.drawable.ic_search_white_24dp)
|
||||
@ -137,7 +152,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
R.id.nav_logout -> launchActivity(LoginActivity())
|
||||
}
|
||||
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -154,8 +169,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
Closes the drawer if it is open, when we press the back button
|
||||
*/
|
||||
override fun onBackPressed() {
|
||||
if(drawerLayout.isDrawerOpen(GravityCompat.START)){
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
if(drawer_layout.isDrawerOpen(GravityCompat.START)){
|
||||
drawer_layout.closeDrawer(GravityCompat.START)
|
||||
} else {
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
@ -1,39 +1,11 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
|
||||
@Database(entities = [PostEntity::class], version = 1)
|
||||
@TypeConverters(Converters::class)
|
||||
@Database(entities = [InstanceDatabaseEntity::class, UserDatabaseEntity::class], version = 1)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun postDao(): PostDao
|
||||
val MAX_NUMBER_OF_POSTS = 100
|
||||
abstract fun instanceDao(): InstanceDao
|
||||
|
||||
companion object {
|
||||
// Singleton prevents multiple instances of database opening at the
|
||||
// same time.
|
||||
@Volatile
|
||||
private var INSTANCE: AppDatabase? = null
|
||||
var TEST_MODE = false
|
||||
|
||||
fun getDatabase(context: Context): AppDatabase {
|
||||
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
// To be able to create a temporary database that flushes when tests are over
|
||||
var instance = if (TEST_MODE) {
|
||||
Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).allowMainThreadQueries().build()
|
||||
} else {
|
||||
Room.databaseBuilder(
|
||||
context.applicationContext, AppDatabase::class.java, "posts_database"
|
||||
).build()
|
||||
}
|
||||
|
||||
INSTANCE = instance
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
abstract fun userDao(): UserDao
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import java.util.Date
|
||||
|
||||
class Converters {
|
||||
@TypeConverter
|
||||
fun fromTimestamp(value: Long?): Date? {
|
||||
return value?.let { Date(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun dateToTimestamp(date: Date?): Long? {
|
||||
return date?.time?.toLong()
|
||||
}
|
||||
}
|
15
app/src/main/java/com/h/pixeldroid/db/InstanceDao.kt
Normal file
15
app/src/main/java/com/h/pixeldroid/db/InstanceDao.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface InstanceDao {
|
||||
@Query("SELECT * FROM instances")
|
||||
fun getAll(): List<InstanceDatabaseEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertInstance(instance: InstanceDatabaseEntity)
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.h.pixeldroid.objects.Instance
|
||||
|
||||
@Entity(tableName = "instances")
|
||||
data class InstanceDatabaseEntity (
|
||||
@PrimaryKey var uri: String,
|
||||
var title: String = "",
|
||||
var max_toot_chars: Int = Instance.DEFAULT_MAX_TOOT_CHARS,
|
||||
var thumbnail: String = ""
|
||||
)
|
@ -1,36 +0,0 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.REPLACE
|
||||
import androidx.room.Query
|
||||
import java.util.Date
|
||||
|
||||
@Dao
|
||||
interface PostDao {
|
||||
@Query("SELECT * FROM posts")
|
||||
fun getAll(): LiveData<List<PostEntity>>
|
||||
|
||||
@Query("SELECT * FROM posts WHERE uid = :postId")
|
||||
fun getById(postId: Int): PostEntity
|
||||
|
||||
@Query("SELECT count(*) FROM posts")
|
||||
fun getPostsCount(): Int
|
||||
|
||||
@Query("UPDATE posts SET date = :date WHERE uid = :postId")
|
||||
fun addDateToPost(postId: Int, date: Date)
|
||||
|
||||
@Query("DELETE FROM posts")
|
||||
fun deleteAll()
|
||||
|
||||
@Query("DELETE FROM posts WHERE date IN (SELECT min(date) FROM posts) ")
|
||||
fun deleteOldestPost(): Int
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
fun insertAll(vararg posts: PostEntity)
|
||||
|
||||
@Delete
|
||||
fun delete(post: PostEntity)
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.PrimaryKey;
|
||||
import java.util.Date
|
||||
|
||||
@Entity(tableName= "posts")
|
||||
data class PostEntity(
|
||||
@PrimaryKey val uid: Int,
|
||||
@ColumnInfo(name = "domain") val domain: String? = "",
|
||||
@ColumnInfo(name = "username") val username: String? = "",
|
||||
@ColumnInfo(name = "display name") val displayName: String? = "",
|
||||
@ColumnInfo(name = "accountID") val accountID: Int? = -1,
|
||||
@ColumnInfo(name = "image url") val ImageURL: String? = "",
|
||||
@ColumnInfo(name = "date") val date: Date?
|
||||
)
|
21
app/src/main/java/com/h/pixeldroid/db/UserDao.kt
Normal file
21
app/src/main/java/com/h/pixeldroid/db/UserDao.kt
Normal file
@ -0,0 +1,21 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
|
||||
@Dao
|
||||
interface UserDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertUser(user: UserDatabaseEntity)
|
||||
|
||||
@Query("SELECT * FROM users")
|
||||
fun getAll(): List<UserDatabaseEntity>
|
||||
|
||||
@Query("SELECT * FROM users WHERE user_id=:id LIMIT 1")
|
||||
fun getUserWithId(id: String): UserDatabaseEntity
|
||||
|
||||
@Query("UPDATE users SET username = :username, display_name = :display_name, avatar_static = :avatar_static WHERE user_id=:user_id")
|
||||
fun updateUser(user_id: String, username: String, display_name: String, avatar_static: String)
|
||||
}
|
23
app/src/main/java/com/h/pixeldroid/db/UserDatabaseEntity.kt
Normal file
23
app/src/main/java/com/h/pixeldroid/db/UserDatabaseEntity.kt
Normal file
@ -0,0 +1,23 @@
|
||||
package com.h.pixeldroid.db
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.ForeignKey
|
||||
|
||||
@Entity(
|
||||
tableName = "users",
|
||||
primaryKeys = ["user_id", "instance_uri"],
|
||||
foreignKeys = [ForeignKey(
|
||||
entity = InstanceDatabaseEntity::class,
|
||||
parentColumns = arrayOf("uri"),
|
||||
childColumns = arrayOf("instance_uri"),
|
||||
onUpdate = ForeignKey.CASCADE,
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)]
|
||||
)
|
||||
data class UserDatabaseEntity (
|
||||
var user_id: String,
|
||||
var instance_uri: String,
|
||||
var username: String,
|
||||
var display_name: String,
|
||||
var avatar_static: String
|
||||
)
|
@ -1,28 +1,23 @@
|
||||
package com.h.pixeldroid
|
||||
package com.h.pixeldroid.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.Toast
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.utils.ImageUtils
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
import java.io.Serializable
|
||||
|
||||
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
|
||||
private const val IMG_URL = "imgurl"
|
||||
@ -96,13 +91,11 @@ class ImageFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
//Load the image into to view
|
||||
val imageView : ImageView = view.findViewById(R.id.imageImageView)!!
|
||||
val picRequest = Glide.with(this)
|
||||
Glide.with(this)
|
||||
.asDrawable().fitCenter()
|
||||
.placeholder(ColorDrawable(Color.GRAY))
|
||||
|
||||
picRequest.load(imgUrl).into(imageView)
|
||||
|
||||
.load(imgUrl)
|
||||
.into(view.findViewById(R.id.imageImageView)!!)
|
||||
}
|
||||
|
||||
companion object {
|
@ -0,0 +1,35 @@
|
||||
package com.h.pixeldroid.fragments.feeds
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
||||
import com.h.pixeldroid.R
|
||||
import kotlinx.android.synthetic.main.fragment_feed.view.feed_fragment_placeholder_text
|
||||
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* Use the [OfflineFeedFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
*/
|
||||
class OfflineFeedFragment: Fragment() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
// Inflate the layout for this fragment
|
||||
val view = inflater.inflate(R.layout.fragment_offline_feed, container, false)
|
||||
view.feed_fragment_placeholder_text.visibility = View.VISIBLE
|
||||
return view
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -23,7 +23,7 @@ data class Account(
|
||||
val acct: String = "",
|
||||
val url: String = "", //HTTPS URL
|
||||
//Display attributes
|
||||
val display_name: String? = null,
|
||||
val display_name: String = "",
|
||||
val note: String = "", //HTML
|
||||
val avatar: String = "", //URL
|
||||
val avatar_static: String = "", //URL
|
||||
|
@ -3,10 +3,14 @@ package com.h.pixeldroid.objects
|
||||
data class Instance (
|
||||
val description: String,
|
||||
val email: String,
|
||||
val max_toot_chars: String = "500",
|
||||
val max_toot_chars: String = DEFAULT_MAX_TOOT_CHARS.toString(),
|
||||
val registrations: Boolean,
|
||||
val thumbnail: String,
|
||||
val title: String,
|
||||
val uri: String,
|
||||
val version: String
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
const val DEFAULT_MAX_TOOT_CHARS = 500
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package com.h.pixeldroid.objects
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spanned
|
||||
@ -18,13 +17,12 @@ import android.widget.Toast
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.ImageView
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.ImageFragment
|
||||
import com.h.pixeldroid.fragments.ImageFragment
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||
@ -40,12 +38,9 @@ import com.h.pixeldroid.utils.PostUtils.Companion.toggleCommentInput
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.undoReblogPost
|
||||
import com.karumi.dexter.Dexter
|
||||
import com.karumi.dexter.PermissionToken
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse
|
||||
import com.karumi.dexter.listener.PermissionRequest
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener
|
||||
import com.karumi.dexter.listener.single.PermissionListener
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postDate
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postDomain
|
||||
import java.io.Serializable
|
||||
@ -124,13 +119,8 @@ data class Status(
|
||||
|
||||
}
|
||||
|
||||
fun getUsername() : CharSequence {
|
||||
var name = account.username
|
||||
if (name.isNullOrEmpty()) {
|
||||
name = account.display_name?: "NoName"
|
||||
}
|
||||
return name
|
||||
}
|
||||
fun getUsername() : CharSequence =
|
||||
account.username.ifBlank{account.display_name.ifBlank{"NoName"}}
|
||||
|
||||
fun getNLikes() : CharSequence {
|
||||
val nLikes = favourites_count
|
||||
|
39
app/src/main/java/com/h/pixeldroid/utils/DBUtils.kt
Normal file
39
app/src/main/java/com/h/pixeldroid/utils/DBUtils.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.objects.Account
|
||||
|
||||
class DBUtils {
|
||||
companion object {
|
||||
fun initDB(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java, "pixeldroid"
|
||||
).allowMainThreadQueries().build()
|
||||
}
|
||||
|
||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String = "") {
|
||||
if (instance_uri.isEmpty()) {
|
||||
db.userDao().updateUser(
|
||||
user_id = account.id,
|
||||
username = account.username,
|
||||
display_name = account.display_name,
|
||||
avatar_static = account.avatar_static
|
||||
)
|
||||
} else {
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = account.id,
|
||||
instance_uri = instance_uri,
|
||||
username = account.username,
|
||||
display_name = account.display_name,
|
||||
avatar_static = account.avatar_static
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package com.h.pixeldroid.utils
|
||||
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.PostEntity
|
||||
import java.util.Calendar
|
||||
|
||||
class DatabaseUtils {
|
||||
companion object {
|
||||
/**
|
||||
* Inserts one post into the specified database,
|
||||
* after it has checked the LRU
|
||||
*/
|
||||
fun insertPost(db: AppDatabase, post: PostEntity) {
|
||||
if (!IsInsertable(db)) {
|
||||
removeEldestPost(db)
|
||||
}
|
||||
|
||||
db.postDao().addDateToPost(post.uid, Calendar.getInstance().time)
|
||||
db.postDao().insertAll(post)
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts multiple posts into the specified database
|
||||
*/
|
||||
fun insertAllPosts(db: AppDatabase, vararg posts: PostEntity) {
|
||||
posts.forEach { insertPost(db, it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if we can add one post into the database
|
||||
* or if it is full
|
||||
*/
|
||||
private fun IsInsertable(db: AppDatabase): Boolean {
|
||||
return db.postDao().getPostsCount() + 1 <= db.MAX_NUMBER_OF_POSTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the eldest post from the database
|
||||
*/
|
||||
private fun removeEldestPost(db: AppDatabase) {
|
||||
db.postDao().deleteOldestPost()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -32,7 +32,7 @@ class HtmlUtils {
|
||||
return result.trim().toSpanned()
|
||||
}
|
||||
|
||||
public fun getDomain(urlString: String?): String {
|
||||
fun getDomain(urlString: String?): String {
|
||||
val uri: URI
|
||||
try {
|
||||
uri = URI(urlString!!)
|
||||
|
17
app/src/main/java/com/h/pixeldroid/utils/Utils.kt
Normal file
17
app/src/main/java/com/h/pixeldroid/utils/Utils.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import androidx.room.Room
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
|
||||
class Utils {
|
||||
companion object {
|
||||
fun hasInternet(context: Context): Boolean {
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
return cm.activeNetwork != null
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -25,13 +25,47 @@
|
||||
android:layout_marginBottom="20dp"
|
||||
app:srcCompat="@drawable/ic_fred_phone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/login_activity_instance_chooser_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="60dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_activity_instance_chooser_offline_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/you_are_in_offline_mode"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/login_activity_instance_chooser"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="15dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/login_activity_instance_chooser_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/enter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/domainTextInputLayout"
|
||||
android:id="@+id/login_activity_instance_input_layout"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:hint="Domain of your instance"
|
||||
app:errorEnabled="true">
|
||||
android:hint="@string/domain_of_your_instance"
|
||||
app:errorEnabled="true"
|
||||
android:visibility="gone">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editText"
|
||||
@ -45,8 +79,7 @@
|
||||
android:id="@+id/connect_instance_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="36dp"
|
||||
android:text="Connect to Pixelfed" />
|
||||
android:text="@string/connect_to_pixelfed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/whatsAnInstanceTextView"
|
||||
@ -61,6 +94,14 @@
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/login_activity_connection_required_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/login_connection_required_once"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/progressLayout"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -3,6 +3,16 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feed_fragment_placeholder_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="25sp"
|
||||
android:text="Nothing to see here!"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
@ -15,9 +25,9 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
app:layoutManager="LinearLayoutManager" />
|
||||
app:layoutManager="LinearLayoutManager"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
|
@ -3,7 +3,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ImageFragment">
|
||||
tools:context=".fragments.ImageFragment">
|
||||
|
||||
|
||||
<ImageView
|
||||
|
16
app/src/main/res/layout/fragment_offline_feed.xml
Normal file
16
app/src/main/res/layout/fragment_offline_feed.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feed_fragment_placeholder_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="25sp"
|
||||
android:text="Nothing to see here!"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -48,5 +48,15 @@
|
||||
<string name="image_download_downloading">Downloading…</string>
|
||||
<string name="image_download_success">Image downloaded successfully</string>
|
||||
<string name="share_picture">Share picture…</string>
|
||||
<string name="domain_of_your_instance">Domain of your instance</string>
|
||||
<string name="connect_to_pixelfed">Connect to Pixelfed</string>
|
||||
<string name="login_connection_required_once">You need to connect to the internet at least once to use PixelDroid :(</string>
|
||||
<string name="you_are_in_offline_mode">You are in offline mode, but you can still view some content!</string>
|
||||
<string name="enter">Enter</string>
|
||||
<string name="auth_error_toast_msg">Server has responded with an error, try again!</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="login_empty_string_error">Instance address cannot be empty!</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user