Merge with master
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
name: Bug
|
||||
about: Report a bug you found in the app
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
* [ ] I checked the release notes for known issues
|
||||
* [ ] I checked the existing issues
|
||||
* [ ] I checked that the issue is not related to my instance configuration, and that my instance has the latest version of pixelfed
|
||||
|
||||
Describe step by step how to reproduce the bug, include screenshots if possible.
|
|
@ -13,3 +13,4 @@
|
|||
.externalNativeBuild
|
||||
.cxx
|
||||
.idea
|
||||
app/release
|
||||
|
|
13
README.md
|
@ -2,4 +2,15 @@
|
|||
![Pixeldroid project logo](pixeldroid_logo.png)
|
||||
Free (as in freedom) Android client for Pixelfed, the federated image sharing platform.
|
||||
|
||||
[![Build Status](https://gitlab.com/Matttter/PixelDroid/badges/master/pipeline.svg)](https://gitlab.com/Matttter/PixelDroid/pipelines) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/test_coverage)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/test_coverage) [![Translation status](https://weblate.pixeldroid.org/widgets/pixeldroid/-/pixeldroid/svg-badge.svg)](https://weblate.pixeldroid.org/engage/pixeldroid/?utm_source=widget)
|
||||
[![Build Status](https://gitlab.com/Matttter/PixelDroid/badges/master/pipeline.svg)](https://gitlab.com/Matttter/PixelDroid/pipelines) [![Maintainability](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/maintainability)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/a4f1747dc60b96eb74df/test_coverage)](https://codeclimate.com/github/H-PixelDroid/PixelDroid/test_coverage) [![Translation status](https://weblate.pixeldroid.org/widgets/pixeldroid/-/pixeldroid/svg-badge.svg)](https://weblate.pixeldroid.org/engage/pixeldroid/?utm_source=widget)
|
||||
|
||||
<a href=https://apt.izzysoft.de/fdroid/index/apk/com.h.pixeldroid><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="170"></a>
|
||||
|
||||
## Compiling the code yourself
|
||||
If you want to try out PixelDroid on your own device, you can try to compile the source code yourself. To do that you will need to install [Android Studio](https://developer.android.com/studio/).
|
||||
|
||||
- Open the ___gradle___ project inside of Android Studio. Then you should plug your Android device into your computer (make sure that your device is in [developer mode](https://developer.android.com/studio/debug/dev-options)) and select ___share files___ on it.
|
||||
|
||||
- You should see that Android studio has detected your device and its name should appear next to a small play button on the top right corner of Android Studio. If that is the case, then you can click said play button and, after Android studio will have built the project, you'll be able to use PixelDroid on your device!
|
||||
|
||||
At this point PixelDroid will be installed on your phone, so it won't have to be plugged in anymore!
|
||||
|
|
|
@ -19,11 +19,14 @@ android {
|
|||
minSdkVersion 23
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "1.0.alpha1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
}
|
||||
lintOptions{
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/java'
|
||||
test.java.srcDirs += 'src/test/java'
|
||||
|
@ -62,22 +65,25 @@ dependencies {
|
|||
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
|
||||
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.8.1'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.16'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.17'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation "androidx.browser:browser:1.2.0"
|
||||
implementation 'com.google.android.material:material:1.1.0'
|
||||
implementation 'com.github.connyduck:sparkbutton:3.0.0'
|
||||
implementation 'com.github.connyduck:sparkbutton:4.0.0'
|
||||
|
||||
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'
|
||||
implementation 'com.github.yalantis:ucrop:2.2.5-native'
|
||||
|
||||
implementation("com.github.bumptech.glide:glide:4.11.0") {
|
||||
exclude group: "com.android.support"
|
||||
|
@ -88,7 +94,7 @@ dependencies {
|
|||
// Excludes the support library because it's already included by Glide.
|
||||
transitive = false
|
||||
}
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
testImplementation "com.github.tomakehurst:wiremock-jre8:2.26.3"
|
||||
|
@ -104,6 +110,21 @@ dependencies {
|
|||
androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.2'
|
||||
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0'
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
|
||||
|
||||
implementation "com.mikepenz:materialdrawer:8.0.3"
|
||||
|
||||
//required support lib modules
|
||||
implementation "androidx.annotation:annotation:1.1.0"
|
||||
|
||||
// Add for NavController support
|
||||
implementation "com.mikepenz:materialdrawer-nav:8.0.3"
|
||||
|
||||
//iconics
|
||||
implementation "com.mikepenz:materialdrawer-iconics:8.0.3"
|
||||
implementation 'com.mikepenz:google-material-typeface:3.0.1.4.original-kotlin@aar'
|
||||
|
||||
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
|
@ -111,7 +132,21 @@ dependencies {
|
|||
def fragment_version = '1.2.4'
|
||||
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
|
||||
|
||||
implementation 'com.karumi:dexter:6.1.0'
|
||||
// Use the most recent version of CameraX
|
||||
def camerax_version = '1.0.0-beta03'
|
||||
implementation "androidx.camera:camera-core:${camerax_version}"
|
||||
implementation "androidx.camera:camera-camera2:${camerax_version}"
|
||||
// CameraX Lifecycle library
|
||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||
|
||||
// CameraX View class
|
||||
implementation 'androidx.camera:camera-view:1.0.0-alpha10'
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_CHOOSER
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.media.MediaScannerConnection
|
||||
import android.os.Environment
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.fragment.app.testing.launchFragmentInContainer
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.h.pixeldroid.fragments.CameraFragment
|
||||
import kotlinx.android.synthetic.main.camera_ui_container.*
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
|
||||
class CameraTest {
|
||||
|
||||
@get:Rule
|
||||
val mRuntimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
|
||||
private fun File.writeBitmap(bitmap: Bitmap) {
|
||||
outputStream().use { out ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
Intents.init()
|
||||
}
|
||||
@After
|
||||
fun after(){
|
||||
Intents.release()
|
||||
}
|
||||
@Test
|
||||
fun takePictureButton() {
|
||||
var scenario = launchFragmentInContainer<CameraFragment>()
|
||||
|
||||
scenario.onFragment {
|
||||
val image = Bitmap.createBitmap(500, 500, Bitmap.Config.ARGB_8888)
|
||||
image.eraseColor(Color.GREEN)
|
||||
val folder =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
|
||||
if (!folder.exists()) {
|
||||
folder.mkdir()
|
||||
}
|
||||
val file = File.createTempFile("temp_img", ".png", folder)
|
||||
file.writeBitmap(image)
|
||||
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val mimeType = MimeTypeMap.getSingleton()
|
||||
.getMimeTypeFromExtension(file.extension)
|
||||
MediaScannerConnection.scanFile(
|
||||
context,
|
||||
arrayOf(file.absolutePath),
|
||||
arrayOf(mimeType)){_, _ ->
|
||||
}
|
||||
|
||||
}
|
||||
scenario = launchFragmentInContainer<CameraFragment>()
|
||||
|
||||
Thread.sleep(2000)
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.camera_capture_button.performClick()
|
||||
}
|
||||
scenario.onFragment { fragment ->
|
||||
assert(fragment.isHidden)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun uploadButton() {
|
||||
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
|
||||
IntentMatchers.hasAction(ACTION_CHOOSER)
|
||||
)
|
||||
|
||||
val scenario = launchFragmentInContainer<CameraFragment>()
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.photo_view_button.performClick()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
|
||||
Intents.intended(expectedIntent)
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun switchButton() {
|
||||
val scenario = launchFragmentInContainer<CameraFragment>()
|
||||
scenario.onFragment { fragment ->
|
||||
fragment.camera_switch_button.performClick()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
scenario.onFragment { fragment ->
|
||||
assert(!fragment.isHidden)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,26 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Gravity
|
||||
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
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.contrib.DrawerMatchers
|
||||
import androidx.test.espresso.contrib.NavigationViewActions
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
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 com.h.pixeldroid.adapters.ProfilePostsRecyclerViewAdapter
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
import androidx.test.uiautomator.UiDevice
|
||||
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
|
||||
|
@ -27,40 +31,81 @@ import org.junit.runner.RunWith
|
|||
class DrawerMenuTest {
|
||||
|
||||
private val mockServer = MockServer()
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(30)
|
||||
@get:Rule
|
||||
var activityRule: ActivityScenarioRule<MainActivity>
|
||||
= ActivityScenarioRule(MainActivity::class.java)
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
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()
|
||||
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test
|
||||
fun testDrawerSettingsButton() {
|
||||
// Start the screen of your activity.
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_settings))
|
||||
onView(withText(R.string.menu_settings)).perform(click())
|
||||
// Check that settings activity was opened.
|
||||
onView(withText(R.string.signature_title)).check(matches(isDisplayed()))
|
||||
onView(withText(R.string.theme_title)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testThemeSettings() {
|
||||
// Start the screen of your activity.
|
||||
onView(withText(R.string.menu_settings)).perform(click())
|
||||
val themes = getInstrumentation().targetContext.resources.getStringArray(R.array.theme_entries)
|
||||
//select theme modes
|
||||
onView(withText(R.string.theme_title)).perform(click())
|
||||
onView(withText(themes[2])).perform(click())
|
||||
|
||||
//Select an other theme
|
||||
onView(withText(R.string.theme_title)).perform(click())
|
||||
onView(withText(themes[0])).perform(click())
|
||||
|
||||
//Select the last theme
|
||||
onView(withText(R.string.theme_title)).perform(click())
|
||||
onView(withText(themes[1])).perform(click())
|
||||
|
||||
//Check that we are back in the settings page
|
||||
onView(withText(R.string.theme_header)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDrawerLogoutButton() {
|
||||
// Start the screen of your activity.
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_logout))
|
||||
onView(withText(R.string.logout)).perform(click())
|
||||
// Check that settings activity was opened.
|
||||
onView(withId(R.id.connect_instance_button)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
@ -68,38 +113,38 @@ class DrawerMenuTest {
|
|||
@Test
|
||||
fun testDrawerProfileButton() {
|
||||
// Start the screen of your activity.
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
// Check that profile activity was opened.
|
||||
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
/*@Test
|
||||
fun testDrawerAvatarClick() {
|
||||
// Start the screen of your activity.
|
||||
onView(withId(R.id.drawer_avatar)).perform(ViewActions.click())
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
// Check that profile activity was opened.
|
||||
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
|
||||
}
|
||||
}*/
|
||||
|
||||
@Test
|
||||
/*@Test
|
||||
fun testDrawerAccountNameClick() {
|
||||
// Start the screen of your activity.
|
||||
onView(withId(R.id.drawer_account_name)).perform(ViewActions.click())
|
||||
onView(withText("Testi")).perform(click())
|
||||
// Check that profile activity was opened.
|
||||
onView(withId(R.id.profilePictureImageView)).check(matches(isDisplayed()))
|
||||
}
|
||||
onView(withText("Add Account")).check(matches(isDisplayed()))
|
||||
}*/
|
||||
|
||||
@Test
|
||||
fun clickFollowers() {
|
||||
// Open My Profile from drawer
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
// Open followers list
|
||||
onView(withId(R.id.nbFollowersTextView)).perform(ViewActions.click())
|
||||
onView(withId(R.id.nbFollowersTextView)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
// Open follower's profile
|
||||
onView(withText("ete2")).perform(ViewActions.click())
|
||||
onView(withText("ete2")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("Christian")))
|
||||
|
@ -108,13 +153,13 @@ class DrawerMenuTest {
|
|||
@Test
|
||||
fun clickFollowing() {
|
||||
// Open My Profile from drawer
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
// Open followers list
|
||||
onView(withId(R.id.nbFollowingTextView)).perform(ViewActions.click())
|
||||
onView(withId(R.id.nbFollowingTextView)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
// Open following's profile
|
||||
onView(withText("Dobios")).perform(ViewActions.click())
|
||||
onView(withText("Dobios")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("Andrew Dobis")))
|
||||
|
@ -123,7 +168,7 @@ class DrawerMenuTest {
|
|||
@Test
|
||||
fun showBookmarkedPosts() {
|
||||
// Open My Profile from drawer
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_account))
|
||||
onView(withText(R.string.menu_account)).perform(click())
|
||||
Thread.sleep(100)
|
||||
// Open bookmarks tab
|
||||
onView(withId(R.id.profile_view_pager))
|
||||
|
@ -133,9 +178,17 @@ class DrawerMenuTest {
|
|||
|
||||
// Open first post
|
||||
onView(withId(R.id.profilePostsRecyclerView))
|
||||
.perform(RecyclerViewActions.actionOnItemAtPosition<ProfilePostsRecyclerViewAdapter.ViewHolder>
|
||||
(0, CustomMatchers.clickChildViewWithId(R.id.postPreview)))
|
||||
.perform(
|
||||
RecyclerViewActions.actionOnItemAtPosition<ProfilePostsRecyclerViewAdapter.ViewHolder>
|
||||
(0, CustomMatchers.clickChildViewWithId(R.id.postPreview))
|
||||
)
|
||||
|
||||
onView(withId(R.id.nlikes)).check(matches(withText("5 Likes")))
|
||||
}
|
||||
|
||||
fun onBackPressedClosesDrawer() {
|
||||
UiDevice.getInstance(getInstrumentation()).pressBack()
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.drawer_layout)).check(matches(DrawerMatchers.isClosed()))
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
|
@ -20,7 +19,6 @@ import androidx.test.rule.GrantPermissionRule
|
|||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.adapters.ThumbnailAdapter
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import kotlinx.android.synthetic.main.fragment_edit_image.*
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.junit.Assert
|
||||
|
@ -33,7 +31,6 @@ import org.junit.runner.RunWith
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
class EditPhotoTest {
|
||||
|
||||
private val mockServer = MockServer()
|
||||
private lateinit var activity: PhotoEditActivity
|
||||
private lateinit var activityScenario: ActivityScenario<PhotoEditActivity>
|
||||
|
||||
|
@ -46,15 +43,10 @@ class EditPhotoTest {
|
|||
@Before
|
||||
fun before() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
val preferences = context.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
preferences.edit().putString("accessToken", "azerty").apply()
|
||||
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
||||
|
||||
// Launch PhotoEditActivity
|
||||
val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index")
|
||||
val intent = Intent(context, PhotoEditActivity::class.java).putExtra("uri", uri)
|
||||
val intent = Intent(context, PhotoEditActivity::class.java).putExtra("picture_uri", uri)
|
||||
|
||||
activityScenario = ActivityScenario.launch<PhotoEditActivity>(intent).onActivity{a -> activity = a}
|
||||
|
||||
|
@ -108,10 +100,11 @@ class EditPhotoTest {
|
|||
Thread.sleep(1000)
|
||||
Espresso.onView(withId(R.id.recycler_view))
|
||||
.perform(actionOnItemAtPosition<ThumbnailAdapter.MyViewHolder>(5, CustomMatchers.clickChildViewWithId(R.id.thumbnail)))
|
||||
Espresso.onView(withId(R.id.image_preview)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun BirghtnessSaturationContrastTest() {
|
||||
fun BrightnessSaturationContrastTest() {
|
||||
Espresso.onView(withId(R.id.tabs)).perform(selectTabAtPosition(1))
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
@ -139,11 +132,10 @@ class EditPhotoTest {
|
|||
|
||||
@Test
|
||||
fun SaveButton() {
|
||||
Espresso.onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
|
||||
Espresso.onView(withId(R.id.toolbar)).check(matches(isDisplayed()))
|
||||
Espresso.onView(withId(R.id.action_save)).perform(click())
|
||||
Espresso.onView(withId(com.google.android.material.R.id.snackbar_text))
|
||||
.check(matches(withText("Image succesfully saved")))
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -153,4 +145,11 @@ class EditPhotoTest {
|
|||
Espresso.onView(withId(R.id.post_creation_picture_frame)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun croppingIsPossible() {
|
||||
Espresso.onView(withId(R.id.cropImageButton)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
Espresso.onView(withId(R.id.menu_crop)).perform(click())
|
||||
Espresso.onView(withId(R.id.image_preview)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
|
@ -4,32 +4,29 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.text.SpannableString
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.NoMatchingViewException
|
||||
import androidx.test.espresso.UiController
|
||||
import androidx.test.espresso.ViewAction
|
||||
import androidx.test.espresso.action.ViewActions
|
||||
import androidx.test.espresso.assertion.ViewAssertions
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.contrib.DrawerMatchers
|
||||
import androidx.test.espresso.contrib.NavigationViewActions
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.Matchers
|
||||
|
@ -45,6 +42,8 @@ import org.junit.runner.RunWith
|
|||
class IntentTest {
|
||||
|
||||
private val mockServer = MockServer()
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
|
@ -59,10 +58,30 @@ class IntentTest {
|
|||
fun before() {
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
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()
|
||||
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
|
@ -96,7 +115,7 @@ class IntentTest {
|
|||
intended(expectedIntent)
|
||||
}
|
||||
|
||||
fun clickClickableSpanInDescription(textToClick: CharSequence): ViewAction {
|
||||
private fun clickClickableSpanInDescription(textToClick: CharSequence): ViewAction {
|
||||
return object : ViewAction {
|
||||
|
||||
override fun getConstraints(): Matcher<View> {
|
||||
|
@ -104,7 +123,7 @@ class IntentTest {
|
|||
}
|
||||
|
||||
override fun getDescription(): String {
|
||||
return "clicking on a ClickableSpan";
|
||||
return "clicking on a ClickableSpan"
|
||||
}
|
||||
|
||||
override fun perform(uiController: UiController, view: View) {
|
||||
|
@ -116,7 +135,7 @@ class IntentTest {
|
|||
throw NoMatchingViewException.Builder()
|
||||
.includeViewHierarchy(true)
|
||||
.withRootView(textView)
|
||||
.build();
|
||||
.build()
|
||||
}
|
||||
|
||||
// Get the links inside the TextView and check if we find textToClick
|
||||
|
@ -128,9 +147,9 @@ class IntentTest {
|
|||
val start = spannableString.getSpanStart(spanCandidate)
|
||||
val end = spannableString.getSpanEnd(spanCandidate)
|
||||
val sequence = spannableString.subSequence(start, end)
|
||||
if (textToClick.toString().equals(sequence.toString())) {
|
||||
if (textToClick.toString() == sequence.toString()) {
|
||||
span.onClick(textView)
|
||||
return;
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,14 +164,15 @@ class IntentTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
/*@Test
|
||||
fun launchesIntent() {
|
||||
// Open Drawer to click on navigation.
|
||||
ActivityScenario.launch(MainActivity::class.java)
|
||||
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout))
|
||||
.check(ViewAssertions.matches(DrawerMatchers.isClosed(Gravity.LEFT))) // Left Drawer should be closed.
|
||||
.perform(DrawerActions.open()) // Open Drawer
|
||||
Espresso.onView(ViewMatchers.withId(R.id.nav_view))
|
||||
|
||||
Espresso.onView(ViewMatchers.withId(R.id.drawer))
|
||||
.perform(NavigationViewActions.navigateTo(R.id.nav_account))
|
||||
|
||||
val expectedIntent: Matcher<Intent> = CoreMatchers.allOf(
|
||||
|
@ -166,7 +186,7 @@ class IntentTest {
|
|||
Thread.sleep(1000)
|
||||
|
||||
intended(expectedIntent)
|
||||
}
|
||||
} */
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_VIEW
|
||||
import android.net.Uri
|
||||
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.action.ViewActions.scrollTo
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
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.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.rule.ActivityTestRule
|
||||
import org.hamcrest.CoreMatchers.allOf
|
||||
import org.hamcrest.CoreMatchers.anyOf
|
||||
import org.hamcrest.CoreMatchers.containsString
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.After
|
||||
|
@ -39,34 +30,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 +80,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,58 @@
|
|||
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.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
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.utils.DBUtils
|
||||
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()
|
||||
db.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyDBandOfflineModeDisplayCorrectMessage() {
|
||||
ActivityScenario.launch(LoginActivity::class.java)
|
||||
onView(withId(R.id.login_activity_connection_required_text)).check(matches(isDisplayed()))
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
device.openQuickSettings()
|
||||
device.findObject(UiSelector().textContains("airplane")).click()
|
||||
device.pressHome()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
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()
|
||||
db.close()
|
||||
}
|
||||
|
||||
@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.invalid_domain))
|
||||
))
|
||||
}
|
||||
|
||||
@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() {
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = server.getUrl().toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = server.getUrl().toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
pref.edit()
|
||||
.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)
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.main_activity_main_linear_layout)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
|
@ -1,27 +1,37 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.ColorMatrix
|
||||
import android.widget.TextView
|
||||
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
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition
|
||||
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 com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.adapters.ProfilePostsRecyclerViewAdapter
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.clickChildViewWithId
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.first
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.getText
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.second
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.slowSwipeUp
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.typeTextInViewWithId
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.censorColorMatrix
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.uncensorColorMatrix
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -34,21 +44,38 @@ class MockedServerTest {
|
|||
|
||||
private val mockServer = MockServer()
|
||||
private lateinit var activityScenario: ActivityScenario<MainActivity>
|
||||
private lateinit var db: AppDatabase
|
||||
private lateinit var context: Context
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@get:Rule
|
||||
var activityRule: ActivityScenarioRule<MainActivity>
|
||||
= ActivityScenarioRule(MainActivity::class.java)
|
||||
|
||||
@Before
|
||||
fun before(){
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
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()
|
||||
context = ApplicationProvider.getApplicationContext()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
}
|
||||
|
||||
|
@ -120,12 +147,12 @@ class MockedServerTest {
|
|||
Thread.sleep(1000)
|
||||
|
||||
// Unfollow
|
||||
onView(withId(R.id.followButton)).perform((ViewActions.click()))
|
||||
onView(withId(R.id.followButton)).perform((click()))
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.followButton)).check(matches(withText("Follow")))
|
||||
|
||||
// Follow
|
||||
onView(withId(R.id.followButton)).perform((ViewActions.click()))
|
||||
onView(withId(R.id.followButton)).perform((click()))
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.followButton)).check(matches(withText("Unfollow")))
|
||||
}
|
||||
|
@ -177,7 +204,7 @@ class MockedServerTest {
|
|||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withText("Dobios liked your post")).perform(ViewActions.click())
|
||||
onView(withText("Dobios liked your post")).perform(click())
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withText("6 Likes")).check(matches(withId(R.id.nlikes)))
|
||||
|
@ -193,7 +220,7 @@ class MockedServerTest {
|
|||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withText("Dobios followed you")).perform(ViewActions.click())
|
||||
onView(withText("Dobios followed you")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
onView(withText("Andrew Dobis")).check(matches(withId(R.id.accountNameTextView)))
|
||||
}
|
||||
|
@ -208,10 +235,10 @@ class MockedServerTest {
|
|||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withText("Dobios liked your post")).perform(ViewActions.click())
|
||||
onView(withText("Dobios liked your post")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.username)).perform(ViewActions.click())
|
||||
onView(withId(R.id.username)).perform(click())
|
||||
Thread.sleep(10000)
|
||||
onView(withText("Dante")).check(matches(withId(R.id.accountNameTextView)))
|
||||
}
|
||||
|
@ -226,7 +253,7 @@ class MockedServerTest {
|
|||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withText("Clement shared your post")).perform(ViewActions.click())
|
||||
onView(withText("Clement shared your post")).perform(click())
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withText("Clement"))).check(matches(withId(R.id.username)))
|
||||
|
@ -286,7 +313,9 @@ class MockedServerTest {
|
|||
a -> run {
|
||||
//Wait for the feed to load
|
||||
Thread.sleep(1000)
|
||||
//Pick the second photo
|
||||
a.findViewById<TextView>(R.id.sensitiveWarning).performClick()
|
||||
Thread.sleep(1000)
|
||||
//Pick the second photo
|
||||
a.findViewById<TabLayout>(R.id.postTabs).getTabAt(1)?.select()
|
||||
}
|
||||
}
|
||||
|
@ -529,5 +558,86 @@ class MockedServerTest {
|
|||
onView(first(withId(R.id.commentContainer)))
|
||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun censorMatrices() {
|
||||
// Doing these dummy checks as I can not get the matrix property from the ImageView
|
||||
val array: FloatArray = floatArrayOf(
|
||||
0.1f, 0f, 0f, 0f, 0f, // red vector
|
||||
0f, 0.1f, 0f, 0f, 0f, // green vector
|
||||
0f, 0f, 0.1f, 0f, 0f, // blue vector
|
||||
0f, 0f, 0f, 1f, 0f ) // alpha vector
|
||||
|
||||
assert(censorColorMatrix().equals(array))
|
||||
assert(uncensorColorMatrix().equals(ColorMatrix()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnSensitiveWarning() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(1))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(1, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnPostPicture() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(1))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(1, clickChildViewWithId(R.id.postPicture)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(second(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnSensitiveWarningTabs() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(0))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(0, clickChildViewWithId(R.id.sensitiveWarning)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun performClickOnPostPictureTabs() {
|
||||
|
||||
onView(withId(R.id.list)).perform(scrollToPosition<PostViewHolder>(0))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(withId(R.id.list))
|
||||
.perform(actionOnItemAtPosition<PostViewHolder>
|
||||
(0, clickChildViewWithId(R.id.postPicture)))
|
||||
Thread.sleep(1000)
|
||||
|
||||
onView(first(withId(R.id.sensitiveWarning))).check(matches(withEffectiveVisibility(Visibility.GONE)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
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.assertion.ViewAssertions.matches
|
||||
|
@ -11,7 +12,11 @@ import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
|||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
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
|
||||
|
@ -21,7 +26,9 @@ import org.junit.runner.RunWith
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
class PostCreationActivityTest {
|
||||
|
||||
val mockServer = MockServer()
|
||||
private val mockServer = MockServer()
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
|
||||
@get:Rule
|
||||
val globalTimeout: Timeout = Timeout.seconds(30)
|
||||
|
@ -31,10 +38,27 @@ class PostCreationActivityTest {
|
|||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
val preferences = context.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
preferences.edit().putString("accessToken", "azerty").apply()
|
||||
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index")
|
||||
val intent = Intent(context, PostCreationActivity::class.java)
|
||||
.putExtra("picture_uri", uri)
|
||||
|
|
|
@ -16,8 +16,13 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
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 kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.camera_ui_container.*
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
|
@ -50,7 +55,7 @@ class PostCreationFragmentTest {
|
|||
fun uploadButtonLaunchesGalleryIntent() {
|
||||
val expectedIntent: Matcher<Intent> = hasAction(Intent.ACTION_CHOOSER)
|
||||
intending(expectedIntent)
|
||||
onView(withId(R.id.uploadPictureButton)).perform(click())
|
||||
onView(withId(R.id.photo_view_button)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
intended(expectedIntent)
|
||||
}
|
||||
|
@ -65,24 +70,45 @@ class PostFragmentUITests {
|
|||
@get:Rule
|
||||
var rule = ActivityScenarioRule(MainActivity::class.java)
|
||||
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
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()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
a -> a.tabs.getTabAt(2)!!.select()
|
||||
}
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
Thread.sleep(300)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun newPostUiTest() {
|
||||
onView(withId(R.id.uploadPictureButton)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.takePictureButton)).check(matches(isDisplayed()))
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
a -> a.tabs.getTabAt(2)!!.select()
|
||||
}
|
||||
Thread.sleep(1500)
|
||||
onView(withId(R.id.photo_view_button)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.camera_capture_button)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
|
@ -4,30 +4,32 @@ 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
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.longClick
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||
import androidx.test.espresso.matcher.RootMatchers
|
||||
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
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
|
||||
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Application
|
||||
import com.h.pixeldroid.objects.Attachment
|
||||
import com.h.pixeldroid.objects.Status
|
||||
import com.h.pixeldroid.objects.Tag
|
||||
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import org.hamcrest.CoreMatchers
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import org.hamcrest.Matcher
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.*
|
||||
import org.junit.rules.Timeout
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
@ -36,6 +38,7 @@ import org.junit.runner.RunWith
|
|||
class PostTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
|
@ -46,11 +49,27 @@ class PostTest {
|
|||
val mockServer = MockServer()
|
||||
mockServer.start()
|
||||
val baseUrl = mockServer.getUrl()
|
||||
val preferences = context.getSharedPreferences(
|
||||
"com.h.pixeldroid.pref",
|
||||
Context.MODE_PRIVATE)
|
||||
preferences.edit().putString("accessToken", "azerty").apply()
|
||||
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
||||
db = DBUtils.initDB(context)
|
||||
db.clearAllTables()
|
||||
db.instanceDao().insertInstance(
|
||||
InstanceDatabaseEntity(
|
||||
uri = baseUrl.toString(),
|
||||
title = "PixelTest"
|
||||
)
|
||||
)
|
||||
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = "123",
|
||||
instance_uri = baseUrl.toString(),
|
||||
username = "Testi",
|
||||
display_name = "Testi Testo",
|
||||
avatar_static = "some_avatar_url",
|
||||
isActive = true,
|
||||
accessToken = "token"
|
||||
)
|
||||
)
|
||||
db.close()
|
||||
Intents.init()
|
||||
}
|
||||
|
||||
|
@ -166,6 +185,62 @@ class PostTest {
|
|||
Intents.intended(expectedIntent)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
fun getNLikesReturnsCorrectFormat() {
|
||||
val status = Status(id="140364967936397312", uri="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
created_at="2020-03-03T08:00:16.000000Z",
|
||||
account= Account(id="115114166443970560", username="Miike", acct="Miike",
|
||||
url="https://pixelfed.de/Miike", display_name="Miike Duart", note="",
|
||||
avatar="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
avatar_static="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
header="", header_static="", locked=false, emojis= emptyList(), discoverable=false,
|
||||
created_at="2019-12-24T15:42:35.000000Z", statuses_count=71, followers_count=14,
|
||||
following_count=0, moved=null, fields=null, bot=false, source=null),
|
||||
content="""Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""",
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
media_attachments= listOf(
|
||||
Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null)
|
||||
),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
emojis= emptyList(), reblogs_count=0, favourites_count=0, replies_count=0, url="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
in_reply_to_id=null, in_reply_to_account=null, reblog=null, poll=null, card=null, language=null, text=null, favourited=false, reblogged=false, muted=false, bookmarked=false, pinned=false)
|
||||
|
||||
Assert.assertEquals("${status.favourites_count} Likes",
|
||||
status.getNLikes(getInstrumentation().targetContext))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getNSharesReturnsCorrectFormat() {
|
||||
val status = Status(id="140364967936397312", uri="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
created_at="2020-03-03T08:00:16.000000Z",
|
||||
account= Account(id="115114166443970560", username="Miike", acct="Miike",
|
||||
url="https://pixelfed.de/Miike", display_name="Miike Duart", note="",
|
||||
avatar="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
avatar_static="https://pixelfed.de/storage/avatars/011/511/416/644/397/056/0/ZhaopLJWTWJ3hsVCS5pS_avatar.png?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35",
|
||||
header="", header_static="", locked=false, emojis= emptyList(), discoverable=false,
|
||||
created_at="2019-12-24T15:42:35.000000Z", statuses_count=71, followers_count=14,
|
||||
following_count=0, moved=null, fields=null, bot=false, source=null),
|
||||
content="""Day 8 <a href="https://pixelfed.de/discover/tags/rotavicentina?src=hash" title="#rotavicentina" class="u-url hashtag" rel="external nofollow noopener">#rotavicentina</a> <a href="https://pixelfed.de/discover/tags/hiking?src=hash" title="#hiking" class="u-url hashtag" rel="external nofollow noopener">#hiking</a> <a href="https://pixelfed.de/discover/tags/nature?src=hash" title="#nature" class="u-url hashtag" rel="external nofollow noopener">#nature</a>""",
|
||||
visibility=Status.Visibility.public, sensitive=false, spoiler_text="",
|
||||
media_attachments= listOf(
|
||||
Attachment(id="15888", type= Attachment.AttachmentType.image, url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB.jpeg",
|
||||
preview_url="https://pixelfed.de/storage/m/113a3e2124a33b1f5511e531953f5ee48456e0c7/34dd6d9fb1762dac8c7ddeeaf789d2d8fa083c9f/JtjO0eAbELpgO1UZqF5ydrKbCKRVyJUM1WAaqIeB_thumb.jpeg",
|
||||
remote_url=null, text_url=null, description=null, blurhash=null)
|
||||
),
|
||||
application= Application(name="web", website=null, vapid_key=null), mentions=emptyList(),
|
||||
tags= listOf(Tag(name="hiking", url="https://pixelfed.de/discover/tags/hiking", history=null), Tag(name="nature", url="https://pixelfed.de/discover/tags/nature", history=null), Tag(name="rotavicentina", url="https://pixelfed.de/discover/tags/rotavicentina", history=null)),
|
||||
emojis= emptyList(), reblogs_count=0, favourites_count=0, replies_count=0, url="https://pixelfed.de/p/Miike/140364967936397312",
|
||||
in_reply_to_id=null, in_reply_to_account=null, reblog=null, poll=null, card=null, language=null, text=null, favourited=false, reblogged=false, muted=false, bookmarked=false, pinned=false)
|
||||
|
||||
Assert.assertEquals("${status.reblogs_count} Shares",
|
||||
status.getNShares(getInstrumentation().targetContext))
|
||||
}
|
||||
|
||||
@After
|
||||
fun after() {
|
||||
Intents.release()
|
||||
|
|
|
@ -31,6 +31,25 @@ abstract class CustomMatchers {
|
|||
}
|
||||
}
|
||||
|
||||
fun <T> second(matcher: Matcher<T>): Matcher<T>? {
|
||||
return object : BaseMatcher<T>() {
|
||||
var isFirst = true
|
||||
override fun describeTo(description: org.hamcrest.Description?) {
|
||||
description?.appendText("second matching item")
|
||||
}
|
||||
|
||||
override fun matches(item: Any?): Boolean {
|
||||
if (isFirst && matcher.matches(item)) {
|
||||
isFirst = false
|
||||
return false
|
||||
} else if (!isFirst && matcher.matches(item))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param percent can be 1 or 0
|
||||
* 1: swipes all the way up
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
package="com.h.pixeldroid">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.any"
|
||||
|
@ -15,6 +15,7 @@
|
|||
<uses-feature android:name="android.hardware.location.gps" />
|
||||
|
||||
<application
|
||||
android:name=".Pixeldroid"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
@ -76,7 +77,8 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.yalantis.ucrop.UCropActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
tools:ignore="LockedOrientationActivity"
|
||||
android:theme="@style/AppTheme.NoActionBar"/>
|
||||
|
||||
<activity
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.feeds.AccountListFragment
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_ID_TAG
|
||||
import com.h.pixeldroid.objects.Account.Companion.FOLLOWING_TAG
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class FollowsActivity : AppCompatActivity() {
|
||||
var followsFragment = AccountListFragment()
|
||||
private var followsFragment = AccountListFragment()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -25,11 +25,15 @@ class FollowsActivity : AppCompatActivity() {
|
|||
val following = intent.getSerializableExtra(FOLLOWING_TAG) as Boolean
|
||||
|
||||
if(id == null) {
|
||||
val preferences = this.getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
val accessToken = preferences.getString("accessToken", "")
|
||||
val db = DBUtils.initDB(applicationContext)
|
||||
|
||||
val user = db.userDao().getActiveUser()
|
||||
|
||||
val domain = user?.instance_uri.orEmpty()
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
db.close()
|
||||
|
||||
val pixelfedAPI = PixelfedAPI.create(domain)
|
||||
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken").enqueue(object :
|
||||
Callback<Account> {
|
||||
|
|
|
@ -6,15 +6,20 @@ import android.content.Intent
|
|||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
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.objects.Account
|
||||
import com.h.pixeldroid.objects.Application
|
||||
import com.h.pixeldroid.objects.Instance
|
||||
import com.h.pixeldroid.objects.Token
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.DBUtils.Companion.storeInstance
|
||||
import com.h.pixeldroid.utils.Utils
|
||||
import com.h.pixeldroid.utils.Utils.Companion.normalizeDomain
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import okhttp3.HttpUrl
|
||||
import retrofit2.Call
|
||||
|
@ -24,41 +29,50 @@ import retrofit2.Response
|
|||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private val TAG = "Login Activity"
|
||||
companion object {
|
||||
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
|
||||
|
||||
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
|
||||
)
|
||||
if (Utils.hasInternet(applicationContext)) {
|
||||
connect_instance_button.setOnClickListener {
|
||||
registerAppToServer(normalizeDomain(editText.text.toString()))
|
||||
}
|
||||
whatsAnInstanceTextView.setOnClickListener{ whatsAnInstance() }
|
||||
inputVisibility = View.VISIBLE
|
||||
} else {
|
||||
login_activity_connection_required_text.visibility = View.VISIBLE
|
||||
}
|
||||
loadingAnimation(false)
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -66,29 +80,6 @@ class LoginActivity : AppCompatActivity() {
|
|||
loadingAnimation(false)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -106,45 +97,47 @@ class LoginActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
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> {
|
||||
|
||||
try{
|
||||
HttpUrl.Builder().host(normalizedDomain.replace("https://", "")).scheme("https").build()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
return failedRegistration(getString(R.string.invalid_domain))
|
||||
}
|
||||
|
||||
hideKeyboard()
|
||||
loadingAnimation(true)
|
||||
|
||||
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 +158,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 +180,81 @@ 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) {
|
||||
preferences.edit().putString("accessToken", accessToken).apply()
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
saveUserAndInstance(accessToken)
|
||||
wipeSharedSettings()
|
||||
}
|
||||
|
||||
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
|
||||
wipeSharedSettings()
|
||||
}
|
||||
|
||||
private fun wipeSharedSettings(){
|
||||
preferences.edit().remove("domain").remove("clientId").remove("clientSecret")
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun loadingAnimation(on: Boolean){
|
||||
if(on) {
|
||||
domainTextInputLayout.visibility = View.GONE
|
||||
login_activity_instance_input_layout.visibility = View.GONE
|
||||
progressLayout.visibility = View.VISIBLE
|
||||
}
|
||||
else {
|
||||
domainTextInputLayout.visibility = View.VISIBLE
|
||||
login_activity_instance_input_layout.visibility = inputVisibility
|
||||
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) {
|
||||
pixelfedAPI.instance().enqueue(object : Callback<Instance> {
|
||||
override fun onFailure(call: Call<Instance>, t: Throwable) {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
override fun onResponse(call: Call<Instance>, response: Response<Instance>) {
|
||||
if (response.isSuccessful && response.body() != null) {
|
||||
val instance = response.body() as Instance
|
||||
storeInstance(db, instance)
|
||||
storeUser(accessToken, instance.uri)
|
||||
} else {
|
||||
return failedRegistration(getString(R.string.instance_error))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun storeUser(accessToken: String, instance: String) {
|
||||
pixelfedAPI.verifyCredentials("Bearer $accessToken")
|
||||
.enqueue(object : Callback<Account> {
|
||||
override fun onResponse(call: Call<Account>, response: Response<Account>) {
|
||||
if (response.body() != null && response.isSuccessful) {
|
||||
db.userDao().deActivateActiveUser()
|
||||
val user = response.body() as Account
|
||||
DBUtils.addUser(
|
||||
db,
|
||||
user,
|
||||
instance,
|
||||
activeUser = true,
|
||||
accessToken = accessToken
|
||||
)
|
||||
db.close()
|
||||
val intent = Intent(this@LoginActivity, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,122 +2,264 @@ package com.h.pixeldroid
|
|||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
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.bumptech.glide.Glide
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.NewPostFragment
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.fragments.CameraFragment
|
||||
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.PostsFeedFragment
|
||||
import com.h.pixeldroid.fragments.feeds.PublicTimelineFragment
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.Utils.Companion.hasInternet
|
||||
import com.mikepenz.iconics.typeface.library.googlematerial.GoogleMaterial
|
||||
import com.mikepenz.materialdrawer.iconics.iconicsIcon
|
||||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.ProfileDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.interfaces.*
|
||||
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.util.DrawerImageLoader
|
||||
import com.mikepenz.materialdrawer.widget.AccountHeaderView
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
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
|
||||
private lateinit var header: AccountHeaderView
|
||||
private var user: UserDatabaseEntity? = null
|
||||
|
||||
companion object {
|
||||
const val ADD_ACCOUNT_IDENTIFIER: Long = -13
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(R.style.AppTheme_NoActionBar)
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
preferences = getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
db = DBUtils.initDB(applicationContext)
|
||||
|
||||
//get the currently active user
|
||||
user = db.userDao().getActiveUser()
|
||||
|
||||
//Check if we have logged in and gotten an access token
|
||||
if(!preferences.contains("accessToken")){
|
||||
launchActivity(LoginActivity())
|
||||
if (user == null) {
|
||||
launchActivity(LoginActivity(), firstTime = true)
|
||||
} else {
|
||||
setupDrawer()
|
||||
|
||||
val tabs = arrayOf(
|
||||
PostsFeedFragment(),
|
||||
if (hasInternet(applicationContext)) PostsFeedFragment()
|
||||
else OfflineFeedFragment(),
|
||||
searchDiscoverFragment,
|
||||
NewPostFragment(),
|
||||
CameraFragment(),
|
||||
NotificationsFragment(),
|
||||
PublicTimelineFragment()
|
||||
)
|
||||
|
||||
setupTabs(tabs)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupDrawer() {
|
||||
drawerLayout = findViewById(R.id.drawer_layout)
|
||||
val navigationView: NavigationView = findViewById(R.id.nav_view)
|
||||
navigationView.setNavigationItemSelectedListener(this)
|
||||
header = AccountHeaderView(this).apply {
|
||||
headerBackgroundScaleType = ImageView.ScaleType.CENTER_CROP
|
||||
currentHiddenInList = true
|
||||
onAccountHeaderListener = { _: View?, profile: IProfile, current: Boolean ->
|
||||
clickProfile(profile, current)
|
||||
}
|
||||
addProfile(ProfileSettingDrawerItem().apply {
|
||||
identifier = ADD_ACCOUNT_IDENTIFIER
|
||||
nameRes = R.string.add_account_name
|
||||
descriptionRes = R.string.add_account_description
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_add
|
||||
}, 0)
|
||||
attachToSliderView(drawer)
|
||||
dividerBelowHeader = false
|
||||
closeDrawerOnProfileListClick = true
|
||||
}
|
||||
|
||||
// Setup views
|
||||
val accessToken = preferences.getString("accessToken", "")
|
||||
val pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
|
||||
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
|
||||
Glide.with(imageView.context)
|
||||
.load(uri)
|
||||
.placeholder(placeholder)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
val drawerHeader = navigationView.getHeaderView(0)
|
||||
val accountName = drawerHeader.findViewById<TextView>(R.id.drawer_account_name)
|
||||
val avatar = drawerHeader.findViewById<ImageView>(R.id.drawer_avatar)
|
||||
override fun cancel(imageView: ImageView) {
|
||||
Glide.with(imageView.context).clear(imageView)
|
||||
}
|
||||
|
||||
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()) }
|
||||
}
|
||||
override fun placeholder(ctx: Context, tag: String?): Drawable {
|
||||
if (tag == DrawerImageLoader.Tags.PROFILE.name || tag == DrawerImageLoader.Tags.PROFILE_DRAWER_ITEM.name) {
|
||||
return ctx.getDrawable(R.drawable.ic_default_user)!!
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("DRAWER ACCOUNT:", t.toString())
|
||||
}
|
||||
return super.placeholder(ctx, tag)
|
||||
}
|
||||
})
|
||||
|
||||
fillDrawerAccountInfo(user!!.user_id)
|
||||
|
||||
//after setting with the values in the db, we make sure to update the database and apply
|
||||
//with the received one. This happens asynchronously.
|
||||
getUpdatedAccount()
|
||||
|
||||
drawer.itemAdapter.add(
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.menu_account
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_person
|
||||
},
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.menu_settings
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_settings
|
||||
},
|
||||
primaryDrawerItem {
|
||||
nameRes = R.string.logout
|
||||
iconicsIcon = GoogleMaterial.Icon.gmd_close
|
||||
})
|
||||
drawer.onDrawerItemClickListener = { v, drawerItem, position ->
|
||||
when (position){
|
||||
1 -> launchActivity(ProfileActivity())
|
||||
2 -> launchActivity(SettingsActivity())
|
||||
3 -> logOut()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun logOut(){
|
||||
db.userDao().deleteActiveUsers()
|
||||
|
||||
val remainingUsers = db.userDao().getAll()
|
||||
if (remainingUsers.isEmpty()){
|
||||
//no more users, start first-time login flow
|
||||
launchActivity(LoginActivity(), firstTime = true)
|
||||
} else {
|
||||
val newActive = remainingUsers.first()
|
||||
db.userDao().activateUser(newActive.user_id)
|
||||
//relaunch the app
|
||||
launchActivity(MainActivity(), firstTime = true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
private fun getUpdatedAccount(){
|
||||
if (hasInternet(applicationContext)) {
|
||||
val domain = user?.instance_uri.orEmpty()
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
val pixelfedAPI = PixelfedAPI.create(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, domain, accessToken = accessToken)
|
||||
fillDrawerAccountInfo(account.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Account>, t: Throwable) {
|
||||
Log.e("DRAWER ACCOUNT:", t.toString())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//called when switching profiles, or when clicking on current profile
|
||||
private fun clickProfile(profile: IProfile, current: Boolean): Boolean {
|
||||
if(current){
|
||||
launchActivity(ProfileActivity())
|
||||
return false
|
||||
}
|
||||
//Clicked on add new account
|
||||
if(profile.identifier == ADD_ACCOUNT_IDENTIFIER){
|
||||
launchActivity(LoginActivity())
|
||||
return false
|
||||
}
|
||||
|
||||
db.userDao().deActivateActiveUser()
|
||||
db.userDao().activateUser(profile.identifier.toString())
|
||||
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
startActivity(intent)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private inline fun primaryDrawerItem(block: PrimaryDrawerItem.() -> Unit): PrimaryDrawerItem {
|
||||
return PrimaryDrawerItem()
|
||||
.apply {
|
||||
isSelectable = false
|
||||
isIconTinted = true
|
||||
}
|
||||
.apply(block)
|
||||
}
|
||||
|
||||
private fun fillDrawerAccountInfo(account: String) {
|
||||
val users = db.userDao().getAll().toMutableList()
|
||||
users.sortWith(Comparator { l, r ->
|
||||
when {
|
||||
l.isActive && !r.isActive -> -1
|
||||
r.isActive && !l.isActive -> 1
|
||||
else -> 0
|
||||
}
|
||||
})
|
||||
val profiles: MutableList<IProfile> = users.map { user ->
|
||||
ProfileDrawerItem().apply {
|
||||
isSelected = user.isActive
|
||||
nameText = user.display_name
|
||||
iconUrl = user.avatar_static
|
||||
isNameShown = true
|
||||
identifier = user.user_id.toLong()
|
||||
descriptionText = "${user.username}@${user.instance_uri.removePrefix("https://")}"
|
||||
}
|
||||
}.toMutableList()
|
||||
|
||||
// reuse the already existing "add account" item
|
||||
for (profile in header.profiles.orEmpty()) {
|
||||
if (profile.identifier == ADD_ACCOUNT_IDENTIFIER) {
|
||||
profiles.add(profile)
|
||||
break
|
||||
}
|
||||
}
|
||||
header.clear()
|
||||
header.profiles = profiles
|
||||
header.setActiveProfile(account.toLong())
|
||||
}
|
||||
|
||||
|
||||
private fun setupTabs(tabs: Array<Fragment>){
|
||||
|
||||
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)
|
||||
|
@ -128,26 +270,17 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
|||
}.attach()
|
||||
}
|
||||
|
||||
/**
|
||||
When clicking in the drawer menu, go to the corresponding activity
|
||||
*/
|
||||
override fun onNavigationItemSelected(@NonNull item: MenuItem): Boolean {
|
||||
when (item.itemId){
|
||||
R.id.nav_account -> launchActivity(ProfileActivity())
|
||||
R.id.nav_settings -> launchActivity(SettingsActivity())
|
||||
R.id.nav_logout -> launchActivity(LoginActivity())
|
||||
}
|
||||
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
Launches the given activity and put it as the current one
|
||||
Setting argument firstTime to true means the task history will be reset (as if the app were launched anew into
|
||||
this activity)
|
||||
*/
|
||||
private fun launchActivity(activity: AppCompatActivity) {
|
||||
private fun launchActivity(activity: AppCompatActivity, firstTime: Boolean = false) {
|
||||
val intent = Intent(this, activity::class.java)
|
||||
|
||||
if(firstTime){
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
|
@ -155,8 +288,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,16 +1,21 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Point
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.h.pixeldroid.adapters.EditPhotoViewPagerAdapter
|
||||
|
@ -19,6 +24,7 @@ import com.h.pixeldroid.fragments.FilterListFragment
|
|||
import com.h.pixeldroid.interfaces.EditImageFragmentListener
|
||||
import com.h.pixeldroid.interfaces.FilterListFragmentListener
|
||||
import com.h.pixeldroid.utils.NonSwipeableViewPager
|
||||
import com.yalantis.ucrop.UCrop
|
||||
import com.zomato.photofilters.imageprocessors.Filter
|
||||
import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter
|
||||
import com.zomato.photofilters.imageprocessors.subfilters.ContrastSubFilter
|
||||
|
@ -29,6 +35,9 @@ import java.io.File
|
|||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors.newSingleThreadExecutor
|
||||
import java.util.concurrent.Future
|
||||
|
||||
// This is an arbitrary number we are using to keep track of the permission
|
||||
// request. Where an app has multiple context for requesting permission,
|
||||
|
@ -40,12 +49,19 @@ private val REQUIRED_PERMISSIONS = arrayOf(android.Manifest.permission.READ_EXTE
|
|||
|
||||
class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditImageFragmentListener {
|
||||
|
||||
val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
|
||||
private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
|
||||
private val BRIGHTNESS_START = 0
|
||||
private val SATURATION_START = 1.0f
|
||||
private val CONTRAST_START = 1.0f
|
||||
|
||||
private var originalImage: Bitmap? = null
|
||||
private var compressedImage: Bitmap? = null
|
||||
private var compressedOriginalImage: Bitmap? = null
|
||||
private lateinit var filteredImage: Bitmap
|
||||
private lateinit var finalImage: Bitmap
|
||||
|
||||
private var actualFilter: Filter? = null
|
||||
|
||||
private lateinit var filterListFragment: FilterListFragment
|
||||
private lateinit var editImageFragment: EditImageFragment
|
||||
|
||||
|
@ -54,11 +70,12 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
lateinit var viewPager: NonSwipeableViewPager
|
||||
lateinit var tabLayout: TabLayout
|
||||
|
||||
private var brightnessFinal = 0
|
||||
private var saturationFinal = 1.0f
|
||||
private var contrastFinal = 1.0f
|
||||
private var brightnessFinal = BRIGHTNESS_START
|
||||
private var saturationFinal = SATURATION_START
|
||||
private var contrastFinal = CONTRAST_START
|
||||
|
||||
private var resultUri: Uri? = null
|
||||
private var imageUri: Uri? = null
|
||||
private var cropUri: Uri? = null
|
||||
|
||||
object URI {var picture_uri: Uri? = null}
|
||||
|
||||
|
@ -66,20 +83,35 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
System.loadLibrary("NativeImageProcessor")
|
||||
}
|
||||
|
||||
|
||||
companion object{
|
||||
private var executor: ExecutorService = newSingleThreadExecutor()
|
||||
private var future: Future<*>? = null
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_photo_edit)
|
||||
|
||||
URI.picture_uri = intent.getParcelableExtra("uri")
|
||||
|
||||
resultUri = URI.picture_uri
|
||||
|
||||
//TODO move to xml:
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar!!.title = "Edit"
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar!!.setHomeButtonEnabled(true)
|
||||
|
||||
val cropButton: FloatingActionButton = findViewById(R.id.cropImageButton)
|
||||
|
||||
cropUri = intent.getParcelableExtra("picture_uri")
|
||||
|
||||
// set on-click listener
|
||||
cropButton.setOnClickListener {
|
||||
startCrop()
|
||||
}
|
||||
|
||||
loadImage()
|
||||
val file = File.createTempFile("temp_compressed_img", ".png", cacheDir)
|
||||
file.writeBitmap(compressedImage!!)
|
||||
URI.picture_uri = Uri.fromFile(file)
|
||||
|
||||
viewPager = findViewById(R.id.viewPager)
|
||||
tabLayout = findViewById(R.id.tabs)
|
||||
|
@ -88,21 +120,24 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
outputDirectory = getOutputDirectory()
|
||||
}
|
||||
|
||||
/** Use external media if it is available, our app's file directory otherwise */
|
||||
private fun getOutputDirectory(): File {
|
||||
val appContext = applicationContext
|
||||
val mediaDir = externalMediaDirs.firstOrNull()?.let {
|
||||
File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() } }
|
||||
return if (mediaDir != null && mediaDir.exists())
|
||||
mediaDir else appContext.filesDir
|
||||
|
||||
//<editor-fold desc="ON LAUNCH">
|
||||
private fun loadImage() {
|
||||
originalImage = MediaStore.Images.Media.getBitmap(contentResolver, cropUri)
|
||||
compressedImage = resizeImage(originalImage!!.copy(BITMAP_CONFIG, true))
|
||||
compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
filteredImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
image_preview.setImageBitmap(compressedImage)
|
||||
}
|
||||
|
||||
private fun loadImage() {
|
||||
originalImage = MediaStore.Images.Media.getBitmap(contentResolver, URI.picture_uri)
|
||||
private fun resizeImage(image: Bitmap): Bitmap {
|
||||
val display = windowManager.defaultDisplay
|
||||
val size = Point()
|
||||
display.getSize(size)
|
||||
|
||||
filteredImage = originalImage!!.copy(BITMAP_CONFIG, true)
|
||||
finalImage = originalImage!!.copy(BITMAP_CONFIG, true)
|
||||
image_preview.setImageBitmap(originalImage)
|
||||
val newY = size.y * 0.7
|
||||
val scale = newY / image.height
|
||||
return Bitmap.createScaledBitmap(image, (image.width * scale).toInt(), newY.toInt(), true)
|
||||
}
|
||||
|
||||
private fun setupViewPager(viewPager: NonSwipeableViewPager?) {
|
||||
|
@ -142,6 +177,139 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="FILTERS">
|
||||
|
||||
override fun onFilterSelected(filter: Filter) {
|
||||
resetControls()
|
||||
filteredImage = compressedOriginalImage!!.copy(BITMAP_CONFIG, true)
|
||||
image_preview.setImageBitmap(filter.processFilter(filteredImage))
|
||||
compressedImage = filteredImage.copy(BITMAP_CONFIG, true)
|
||||
actualFilter = filter
|
||||
}
|
||||
|
||||
private fun resetControls() {
|
||||
editImageFragment.resetControl()
|
||||
|
||||
brightnessFinal = BRIGHTNESS_START
|
||||
saturationFinal = SATURATION_START
|
||||
contrastFinal = CONTRAST_START
|
||||
}
|
||||
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="EDITS">
|
||||
|
||||
private fun applyFilterAndShowImage(filter: Filter, image: Bitmap?) {
|
||||
future?.cancel(true)
|
||||
future = executor.submit {
|
||||
val bitmap = filter.processFilter(image!!.copy(BITMAP_CONFIG, true))
|
||||
image_preview.post {
|
||||
image_preview.setImageBitmap(bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBrightnessChange(brightness: Int) {
|
||||
brightnessFinal = brightness
|
||||
val myFilter = Filter()
|
||||
myFilter.addEditFilters(brightness, saturationFinal, contrastFinal)
|
||||
applyFilterAndShowImage(myFilter, filteredImage)
|
||||
}
|
||||
|
||||
override fun onSaturationChange(saturation: Float) {
|
||||
saturationFinal = saturation
|
||||
val myFilter = Filter()
|
||||
myFilter.addEditFilters(brightnessFinal, saturation, contrastFinal)
|
||||
applyFilterAndShowImage(myFilter, filteredImage)
|
||||
}
|
||||
|
||||
override fun onContrastChange(contrast: Float) {
|
||||
contrastFinal = contrast
|
||||
val myFilter = Filter()
|
||||
myFilter.addEditFilters(brightnessFinal, saturationFinal, contrast)
|
||||
applyFilterAndShowImage(myFilter, filteredImage)
|
||||
}
|
||||
|
||||
private fun Filter.addEditFilters(br: Int, sa: Float, co: Float): Filter {
|
||||
addSubFilter(BrightnessSubFilter(br))
|
||||
addSubFilter(ContrastSubFilter(co))
|
||||
addSubFilter(SaturationSubfilter(sa))
|
||||
return this
|
||||
}
|
||||
|
||||
override fun onEditStarted() {
|
||||
}
|
||||
|
||||
override fun onEditCompleted() {
|
||||
val myFilter = Filter()
|
||||
myFilter.addEditFilters(brightnessFinal, saturationFinal, contrastFinal)
|
||||
val bitmap = filteredImage.copy(BITMAP_CONFIG, true)
|
||||
|
||||
compressedImage = myFilter.processFilter(bitmap)
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="CROPPING">
|
||||
|
||||
private fun startCrop() {
|
||||
applyFinalFilters(MediaStore.Images.Media.getBitmap(contentResolver, cropUri))
|
||||
val file = File.createTempFile("temp_crop_img", ".png", cacheDir)
|
||||
file.writeBitmap(finalImage)
|
||||
|
||||
val uCrop: UCrop = UCrop.of(Uri.fromFile(file), URI.picture_uri!!)
|
||||
uCrop.start(this)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if(resultCode == Activity.RESULT_OK) {
|
||||
imageUri = data!!.data
|
||||
|
||||
if (requestCode == UCrop.RESULT_ERROR) {
|
||||
handleCropError(data)
|
||||
} else {
|
||||
handleCropResult(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetFilteredImage(){
|
||||
val newBr = if(brightnessFinal != 0) BRIGHTNESS_START/brightnessFinal else 0
|
||||
val newSa = if(saturationFinal != 0.0f) SATURATION_START/saturationFinal else 0.0f
|
||||
val newCo = if(contrastFinal != 0.0f) CONTRAST_START/contrastFinal else 0.0f
|
||||
val myFilter = Filter().addEditFilters(newBr, newSa, newCo)
|
||||
|
||||
filteredImage = myFilter.processFilter(filteredImage)
|
||||
}
|
||||
|
||||
private fun handleCropResult(data: Intent?) {
|
||||
val resultCrop: Uri? = UCrop.getOutput(data!!)
|
||||
if(resultCrop != null) {
|
||||
image_preview.setImageURI(resultCrop)
|
||||
|
||||
val bitmap = (image_preview.drawable as BitmapDrawable).bitmap
|
||||
originalImage = bitmap.copy(Bitmap.Config.ARGB_8888, true)
|
||||
compressedImage = resizeImage(originalImage!!.copy(BITMAP_CONFIG, true))
|
||||
compressedOriginalImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
filteredImage = compressedImage!!.copy(BITMAP_CONFIG, true)
|
||||
resetFilteredImage()
|
||||
} else {
|
||||
Toast.makeText(this, "Cannot retrieve image", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCropError(data: Intent?) {
|
||||
val resultError = UCrop.getError(data!!)
|
||||
if(resultError != null) {
|
||||
Toast.makeText(this, "" + resultError, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(this, "Unexpected Error", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
//</editor-fold>
|
||||
//<editor-fold desc="FLOW">
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
|
@ -156,14 +324,21 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
REQUEST_CODE_PERMISSIONS_SEND_PHOTO -> permissionsGrantedToSave(false)
|
||||
}
|
||||
} else {
|
||||
Snackbar.make(coordinator_edit, "Permission denied", Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(coordinator_edit, getString(R.string.permission_denied),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyFinalFilters(image: Bitmap?) {
|
||||
val editFilter = Filter().addEditFilters(brightnessFinal, saturationFinal, contrastFinal)
|
||||
|
||||
finalImage = editFilter.processFilter(image!!.copy(BITMAP_CONFIG, true))
|
||||
if (actualFilter!=null) finalImage = actualFilter!!.processFilter(finalImage)
|
||||
}
|
||||
|
||||
private fun uploadImage(file: File) {
|
||||
val intent = Intent (applicationContext, PostCreationActivity::class.java)
|
||||
intent.putExtra("picture_uri", Uri.fromFile(file))
|
||||
//file.delete()
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
applicationContext!!.startActivity(intent)
|
||||
}
|
||||
|
@ -189,6 +364,15 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
applicationContext, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
/** Use external media if it is available, our app's file directory otherwise */
|
||||
private fun getOutputDirectory(): File {
|
||||
val appContext = applicationContext
|
||||
val mediaDir = externalMediaDirs.firstOrNull()?.let {
|
||||
File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() } }
|
||||
return if (mediaDir != null && mediaDir.exists())
|
||||
mediaDir else appContext.filesDir
|
||||
}
|
||||
|
||||
private fun File.writeBitmap(bitmap: Bitmap) {
|
||||
outputStream().use { out ->
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 85, out)
|
||||
|
@ -197,74 +381,29 @@ class PhotoEditActivity : AppCompatActivity(), FilterListFragmentListener, EditI
|
|||
}
|
||||
|
||||
private fun permissionsGrantedToSave(save: Boolean) {
|
||||
val file = if(!save){
|
||||
//put picture in cache
|
||||
File.createTempFile("temp_img", ".png", cacheDir)
|
||||
} else{
|
||||
// Save the picture (quality is ignored for PNG)
|
||||
File(outputDirectory, SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
|
||||
val file =
|
||||
if(!save){
|
||||
//put picture in cache
|
||||
File.createTempFile("temp_edit_img", ".png", cacheDir)
|
||||
} else{
|
||||
// Save the picture (quality is ignored for PNG)
|
||||
File(outputDirectory, SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
|
||||
.format(System.currentTimeMillis()) + ".png")
|
||||
}
|
||||
}
|
||||
try {
|
||||
applyFinalFilters(originalImage)
|
||||
file.writeBitmap(finalImage)
|
||||
} catch (e: IOException) {
|
||||
Snackbar.make(coordinator_edit, "Unable to save image", Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(coordinator_edit, getString(R.string.save_image_failed),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
if (!save) {
|
||||
uploadImage(file)
|
||||
} else {
|
||||
Snackbar.make(coordinator_edit, "Image succesfully saved", Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(coordinator_edit, getString(R.string.save_image_success),
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFilterSelected(filter: Filter) {
|
||||
resetControls()
|
||||
filteredImage = originalImage!!.copy(BITMAP_CONFIG, true)
|
||||
image_preview.setImageBitmap(filter.processFilter(filteredImage))
|
||||
finalImage = filteredImage.copy(BITMAP_CONFIG, true)
|
||||
}
|
||||
|
||||
private fun resetControls() {
|
||||
editImageFragment.resetControl()
|
||||
|
||||
brightnessFinal = 0
|
||||
saturationFinal = 1.0f
|
||||
contrastFinal = 1.0f
|
||||
}
|
||||
|
||||
override fun onBrightnessChange(brightness: Int) {
|
||||
brightnessFinal = brightness
|
||||
val myFilter = Filter()
|
||||
myFilter.addSubFilter(BrightnessSubFilter(brightness))
|
||||
image_preview.setImageBitmap(myFilter.processFilter(finalImage.copy(BITMAP_CONFIG, true)))
|
||||
}
|
||||
|
||||
override fun onSaturationChange(saturation: Float) {
|
||||
saturationFinal = saturation
|
||||
val myFilter = Filter()
|
||||
myFilter.addSubFilter(SaturationSubfilter(saturation))
|
||||
image_preview.setImageBitmap(myFilter.processFilter(finalImage.copy(BITMAP_CONFIG, true)))
|
||||
}
|
||||
|
||||
override fun onContrastChange(contrast: Float) {
|
||||
contrastFinal = contrast
|
||||
val myFilter = Filter()
|
||||
myFilter.addSubFilter(ContrastSubFilter(contrast))
|
||||
image_preview.setImageBitmap(myFilter.processFilter(finalImage.copy(BITMAP_CONFIG, true)))
|
||||
}
|
||||
|
||||
override fun onEditStarted() {
|
||||
|
||||
}
|
||||
|
||||
override fun onEditCompleted() {
|
||||
val bitmap = filteredImage.copy(BITMAP_CONFIG, true)
|
||||
val myFilter = Filter()
|
||||
myFilter.addSubFilter(ContrastSubFilter(contrastFinal))
|
||||
myFilter.addSubFilter(SaturationSubfilter(saturationFinal))
|
||||
myFilter.addSubFilter(BrightnessSubFilter(brightnessFinal))
|
||||
|
||||
finalImage = myFilter.processFilter(bitmap)
|
||||
}
|
||||
//</editor-fold>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.app.Application
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.h.pixeldroid.utils.ThemeUtils
|
||||
|
||||
class Pixeldroid: Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
val sharedPreferences =
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
ThemeUtils.setThemeFromPreferences(sharedPreferences, resources)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
|
@ -13,15 +11,16 @@ import com.h.pixeldroid.objects.Status
|
|||
import com.h.pixeldroid.objects.Status.Companion.DISCOVER_TAG
|
||||
import com.h.pixeldroid.objects.Status.Companion.DOMAIN_TAG
|
||||
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import kotlinx.android.synthetic.main.activity_post.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class PostActivity : AppCompatActivity() {
|
||||
private lateinit var preferences: SharedPreferences
|
||||
lateinit var postFragment : PostFragment
|
||||
private lateinit var postFragment : PostFragment
|
||||
lateinit var domain : String
|
||||
private lateinit var accessToken : String
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -29,11 +28,13 @@ class PostActivity : AppCompatActivity() {
|
|||
|
||||
val status = intent.getSerializableExtra(POST_TAG) as Status?
|
||||
val discoverPost: DiscoverPost? = intent.getSerializableExtra(DISCOVER_TAG) as DiscoverPost?
|
||||
val db = DBUtils.initDB(applicationContext)
|
||||
|
||||
preferences = getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
domain = preferences.getString("domain", "")!!
|
||||
val user = db.userDao().getActiveUser()
|
||||
|
||||
domain = user?.instance_uri.orEmpty()
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
db.close()
|
||||
|
||||
postFragment = PostFragment()
|
||||
val arguments = Bundle()
|
||||
|
@ -52,7 +53,6 @@ class PostActivity : AppCompatActivity() {
|
|||
discoverPost: DiscoverPost
|
||||
) {
|
||||
val api = PixelfedAPI.create(domain)
|
||||
val accessToken = preferences.getString("accessToken", "") ?: ""
|
||||
val id = discoverPost.url?.substringAfterLast('/') ?: ""
|
||||
api.getStatus("Bearer $accessToken", id).enqueue(object : Callback<Status> {
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
|
@ -16,8 +14,11 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.net.toUri
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.objects.Attachment
|
||||
import com.h.pixeldroid.objects.Instance
|
||||
import com.h.pixeldroid.objects.Status
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
|
@ -38,26 +39,43 @@ class PostCreationActivity : AppCompatActivity() {
|
|||
|
||||
private lateinit var accessToken: String
|
||||
private lateinit var pixelfedAPI: PixelfedAPI
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var pictureFrame: ImageView
|
||||
private lateinit var image: File
|
||||
private var user: UserDatabaseEntity? = null
|
||||
|
||||
private var maxLength: Int = Instance.DEFAULT_MAX_TOOT_CHARS
|
||||
|
||||
private var description: String = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_post_creation)
|
||||
val imageUri: Uri = intent.getParcelableExtra<Uri>("picture_uri")!!
|
||||
|
||||
val imageUri: Uri = intent.getParcelableExtra("picture_uri")!!
|
||||
|
||||
saveImage(imageUri)
|
||||
|
||||
pictureFrame = findViewById<ImageView>(R.id.post_creation_picture_frame)
|
||||
pictureFrame = findViewById(R.id.post_creation_picture_frame)
|
||||
pictureFrame.setImageURI(image.toUri())
|
||||
|
||||
preferences = getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
accessToken = preferences.getString("accessToken", "")!!
|
||||
val db = DBUtils.initDB(applicationContext)
|
||||
user = db.userDao().getActiveUser()
|
||||
|
||||
val instances = db.instanceDao().getAll()
|
||||
db.close()
|
||||
maxLength = if (user!=null){
|
||||
val thisInstances =
|
||||
instances.filter { instanceDatabaseEntity ->
|
||||
instanceDatabaseEntity.uri.contains(user!!.instance_uri)
|
||||
}
|
||||
thisInstances.first().max_toot_chars
|
||||
} else {
|
||||
Instance.DEFAULT_MAX_TOOT_CHARS
|
||||
}
|
||||
|
||||
val domain = user?.instance_uri.orEmpty()
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
pixelfedAPI = PixelfedAPI.create(domain)
|
||||
|
||||
// check if the picture is alright
|
||||
// TODO
|
||||
|
@ -97,10 +115,10 @@ class PostCreationActivity : AppCompatActivity() {
|
|||
private fun setDescription(): Boolean {
|
||||
val textField = findViewById<TextInputEditText>(R.id.new_post_description_input_field)
|
||||
val content = textField.text.toString()
|
||||
val maxLength = preferences.getInt("max_toot_chars", 500)
|
||||
if (content.length > maxLength) {
|
||||
// error, too much characters
|
||||
textField.error = "Description must contain $maxLength characters at most."
|
||||
textField.error = getString(R.string.description_max_characters).format(maxLength)
|
||||
|
||||
return false
|
||||
}
|
||||
// store the description
|
||||
|
@ -115,7 +133,8 @@ class PostCreationActivity : AppCompatActivity() {
|
|||
Callback<Attachment> {
|
||||
override fun onFailure(call: Call<Attachment>, t: Throwable) {
|
||||
Log.e(TAG, t.toString() + call.request())
|
||||
Toast.makeText(applicationContext,"Picture upload error!",Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_picture_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Attachment>, response: Response<Attachment>) {
|
||||
|
@ -124,10 +143,14 @@ class PostCreationActivity : AppCompatActivity() {
|
|||
if (body.type.name == "image") {
|
||||
post(body.id)
|
||||
} else
|
||||
Toast.makeText(applicationContext, "Upload error: wrong picture format.", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext, getString(R.string.picture_format_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Log.e(TAG, "Server responded: $response" + call.request() + call.request().body)
|
||||
Toast.makeText(applicationContext,"Upload error: bad request format",Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG,
|
||||
"Server responded: $response${call.request()}${call.request().body}"
|
||||
)
|
||||
Toast.makeText(applicationContext,getString(R.string.request_format_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -141,16 +164,19 @@ class PostCreationActivity : AppCompatActivity() {
|
|||
media_ids = listOf(id)
|
||||
).enqueue(object: Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Toast.makeText(applicationContext,"Post upload failed",Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, t.message + call.request())
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
if (response.code() == 200) {
|
||||
Toast.makeText(applicationContext,"Post upload success",Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_success),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
startActivity(Intent(applicationContext, MainActivity::class.java))
|
||||
} else {
|
||||
Toast.makeText(applicationContext,"Post upload failed : not 200",Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext,getString(R.string.upload_post_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, call.request().toString() + response.raw().toString())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
|
@ -24,25 +22,31 @@ import com.h.pixeldroid.fragments.ProfilePostFragment
|
|||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Account.Companion.ACCOUNT_TAG
|
||||
import com.h.pixeldroid.objects.Relationship
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class ProfileActivity : AppCompatActivity() {
|
||||
private lateinit var preferences : SharedPreferences
|
||||
private lateinit var pixelfedAPI : PixelfedAPI
|
||||
private var accessToken : String? = null
|
||||
|
||||
private var account: Account? = null
|
||||
private lateinit var domain : String
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_profile)
|
||||
|
||||
preferences = this.getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE)
|
||||
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
accessToken = preferences.getString("accessToken", "")
|
||||
val db = DBUtils.initDB(applicationContext)
|
||||
|
||||
val user = db.userDao().getActiveUser()
|
||||
|
||||
domain = user?.instance_uri.orEmpty()
|
||||
pixelfedAPI = PixelfedAPI.create(domain)
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
db.close()
|
||||
|
||||
setContent()
|
||||
}
|
||||
|
@ -97,17 +101,20 @@ class ProfileActivity : AppCompatActivity() {
|
|||
accountName.setTypeface(null, Typeface.BOLD)
|
||||
|
||||
val nbPosts = findViewById<TextView>(R.id.nbPostsTextView)
|
||||
nbPosts.text = "${account!!.statuses_count}\nPosts"
|
||||
nbPosts.text = applicationContext.getString(R.string.nb_posts)
|
||||
.format(account!!.statuses_count.toString())
|
||||
nbPosts.setTypeface(null, Typeface.BOLD)
|
||||
|
||||
val nbFollowers = findViewById<TextView>(R.id.nbFollowersTextView)
|
||||
nbFollowers.text = "${account!!.followers_count}\nFollowers"
|
||||
nbFollowers.text = applicationContext.getString(R.string.nb_followers)
|
||||
.format(account!!.followers_count.toString())
|
||||
nbFollowers.setTypeface(null, Typeface.BOLD)
|
||||
// On click open followers list
|
||||
nbFollowers.setOnClickListener{ onClickFollowers() }
|
||||
|
||||
val nbFollowing = findViewById<TextView>(R.id.nbFollowingTextView)
|
||||
nbFollowing.text = "${account!!.following_count}\nFollowing"
|
||||
nbFollowing.text = applicationContext.getString(R.string.nb_following)
|
||||
.format(account!!.following_count.toString())
|
||||
nbFollowing.setTypeface(null, Typeface.BOLD)
|
||||
// On click open followers list
|
||||
nbFollowing.setOnClickListener{ onClickFollowing() }
|
||||
|
@ -138,14 +145,13 @@ class ProfileActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun onClickEditButton() {
|
||||
val url = "${preferences.getString("domain", "")}/settings/home"
|
||||
val url = "$domain/settings/home"
|
||||
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
if(browserIntent.resolveActivity(packageManager) != null) {
|
||||
startActivity(browserIntent)
|
||||
} else {
|
||||
val text = "Cannot open this link"
|
||||
Log.e("ProfileActivity", text)
|
||||
Log.e("ProfileActivity", "Cannot open this link")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +181,8 @@ class ProfileActivity : AppCompatActivity() {
|
|||
|
||||
override fun onFailure(call: Call<List<Relationship>>, t: Throwable) {
|
||||
Log.e("FOLLOW ERROR", t.toString())
|
||||
Toast.makeText(applicationContext,"Could not get follow status", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(applicationContext,getString(R.string.follow_status_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<List<Relationship>>, response: Response<List<Relationship>>) {
|
||||
|
@ -193,8 +200,8 @@ class ProfileActivity : AppCompatActivity() {
|
|||
followButton.visibility = View.VISIBLE
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(applicationContext, "Could not display follow button", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
Toast.makeText(applicationContext, getString(R.string.follow_button_failed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -209,8 +216,8 @@ class ProfileActivity : AppCompatActivity() {
|
|||
|
||||
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||
Log.e("FOLLOW ERROR", t.toString())
|
||||
Toast.makeText(applicationContext, "Could not follow", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
Toast.makeText(applicationContext, getString(R.string.follow_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
|
@ -221,8 +228,8 @@ class ProfileActivity : AppCompatActivity() {
|
|||
followButton.text = "Unfollow"
|
||||
setOnClickUnfollow()
|
||||
} else if (response.code() == 403) {
|
||||
Toast.makeText(applicationContext, "This action is not allowed", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
Toast.makeText(applicationContext, getString(R.string.action_not_allowed),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -238,8 +245,8 @@ class ProfileActivity : AppCompatActivity() {
|
|||
|
||||
override fun onFailure(call: Call<Relationship>, t: Throwable) {
|
||||
Log.e("UNFOLLOW ERROR", t.toString())
|
||||
Toast.makeText(applicationContext, "Could not unfollow", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
Toast.makeText(applicationContext, getString(R.string.unfollow_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Relationship>, response: Response<Relationship>) {
|
||||
|
@ -247,8 +254,8 @@ class ProfileActivity : AppCompatActivity() {
|
|||
followButton.text = "Follow"
|
||||
setOnClickFollow()
|
||||
} else if (response.code() == 401) {
|
||||
Toast.makeText(applicationContext, "The access token is invalid", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
Toast.makeText(applicationContext, getString(R.string.access_token_invalid),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -2,7 +2,6 @@ package com.h.pixeldroid
|
|||
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -17,7 +16,6 @@ import com.h.pixeldroid.fragments.feeds.search.SearchPostsFragment
|
|||
import com.h.pixeldroid.objects.Results
|
||||
|
||||
class SearchActivity : AppCompatActivity() {
|
||||
private lateinit var preferences: SharedPreferences
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -26,11 +24,15 @@ class SearchActivity : AppCompatActivity() {
|
|||
var query = intent.getSerializableExtra("searchFeed") as String
|
||||
query = query.trim()
|
||||
|
||||
val searchType = if (query.startsWith("#")){
|
||||
Results.SearchType.hashtags
|
||||
} else if(query.startsWith("@")){
|
||||
Results.SearchType.accounts
|
||||
} else Results.SearchType.statuses
|
||||
val searchType = when {
|
||||
query.startsWith("#") -> {
|
||||
Results.SearchType.hashtags
|
||||
}
|
||||
query.startsWith("@") -> {
|
||||
Results.SearchType.accounts
|
||||
}
|
||||
else -> Results.SearchType.statuses
|
||||
}
|
||||
|
||||
if(searchType != Results.SearchType.statuses) query = query.drop(1)
|
||||
|
||||
|
@ -75,9 +77,9 @@ class SearchActivity : AppCompatActivity() {
|
|||
val tabLayout = findViewById<TabLayout>(R.id.search_tabs)
|
||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||
when(position){
|
||||
0 -> tab.text = "POSTS"
|
||||
1 -> tab.text = "ACCOUNTS"
|
||||
2 -> tab.text = "HASHTAGS"
|
||||
0 -> tab.text = getString(R.string.posts)
|
||||
1 -> tab.text = getString(R.string.accounts)
|
||||
2 -> tab.text = getString(R.string.hashtags)
|
||||
}
|
||||
}.attach()
|
||||
when(searchType){
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.h.pixeldroid.utils.ThemeUtils.Companion.setThemeFromPreferences
|
||||
|
||||
class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private var restartActivitiesOnExit = false
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContentView(R.layout.settings)
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
|
@ -15,7 +22,31 @@ class SettingsActivity : AppCompatActivity() {
|
|||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
private fun restartCurrentActivity() {
|
||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
super.startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
"theme" -> setThemeFromPreferences(sharedPreferences, resources)
|
||||
}
|
||||
|
||||
restartActivitiesOnExit = true
|
||||
restartCurrentActivity()
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
|
||||
class EditPhotoViewPagerAdapter (manager: FragmentManager):
|
||||
FragmentPagerAdapter(manager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
FragmentPagerAdapter(manager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
private val fragmentList = ArrayList<Fragment>()
|
||||
private val fragmentTitleList = ArrayList<String>()
|
||||
|
|
|
@ -13,8 +13,7 @@ import com.h.pixeldroid.objects.Status
|
|||
import com.h.pixeldroid.utils.ImageConverter.Companion.setSquareImageFromURL
|
||||
|
||||
/**
|
||||
* [RecyclerView.Adapter] that can display a list of [PostMiniature]s and makes a call to the
|
||||
* specified [OnListFragmentInteractionListener].
|
||||
* [RecyclerView.Adapter] that can display a list of [Status]s
|
||||
*/
|
||||
class ProfilePostsRecyclerViewAdapter(
|
||||
private val context: Context
|
||||
|
@ -29,7 +28,11 @@ class ProfilePostsRecyclerViewAdapter(
|
|||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val post = posts[position]
|
||||
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
|
||||
|
||||
if (post.sensitive)
|
||||
setSquareImageFromURL(holder.postView, null, holder.postPreview)
|
||||
else
|
||||
setSquareImageFromURL(holder.postView, post.getPostPreviewURL(), holder.postPreview)
|
||||
|
||||
holder.postPreview.setOnClickListener {
|
||||
val intent = Intent(holder.postPreview.context, PostActivity::class.java)
|
||||
|
|
|
@ -46,12 +46,7 @@ class ThumbnailAdapter (private val context: Context,
|
|||
}
|
||||
|
||||
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
var thumbnail: ImageView
|
||||
var filterName: TextView
|
||||
|
||||
init {
|
||||
thumbnail = itemView.thumbnail
|
||||
filterName = itemView.filter_name
|
||||
}
|
||||
var thumbnail: ImageView = itemView.thumbnail
|
||||
var filterName: TextView = itemView.filter_name
|
||||
}
|
||||
}
|
|
@ -1,39 +1,10 @@
|
|||
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
|
||||
|
||||
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 instanceDao(): InstanceDao
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
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 isActive=1 LIMIT 1")
|
||||
fun getActiveUser(): UserDatabaseEntity?
|
||||
|
||||
@Query("UPDATE users SET isActive=0 WHERE isActive=1")
|
||||
fun deActivateActiveUser()
|
||||
|
||||
@Query("UPDATE users SET isActive=1 WHERE user_id=:id")
|
||||
fun activateUser(id: String)
|
||||
|
||||
@Query("DELETE FROM users WHERE isActive=1")
|
||||
fun deleteActiveUsers()
|
||||
|
||||
@Query("SELECT * FROM users WHERE user_id=:id LIMIT 1")
|
||||
fun getUserWithId(id: String): UserDatabaseEntity
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
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,
|
||||
var isActive: Boolean,
|
||||
var accessToken: String
|
||||
)
|
|
@ -0,0 +1,399 @@
|
|||
package com.h.pixeldroid.fragments
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.camera.core.*
|
||||
import androidx.camera.core.ImageCapture.Metadata
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.h.pixeldroid.PhotoEditActivity
|
||||
import com.h.pixeldroid.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
// This is an arbitrary number we are using to keep track of the permission
|
||||
// request. Where an app has multiple context for requesting permission,
|
||||
// this can help differentiate the different contexts.
|
||||
private const val REQUEST_CODE_PERMISSIONS = 10
|
||||
private const val ANIMATION_FAST_MILLIS = 50L
|
||||
private const val ANIMATION_SLOW_MILLIS = 100L
|
||||
|
||||
/**
|
||||
* Camera fragment
|
||||
*/
|
||||
class CameraFragment : Fragment() {
|
||||
|
||||
private lateinit var container: ConstraintLayout
|
||||
private lateinit var viewFinder: PreviewView
|
||||
private lateinit var outputDirectory: File
|
||||
|
||||
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
private val PICK_IMAGE_REQUEST = 1
|
||||
private val CAPTURE_IMAGE_REQUEST = 2
|
||||
|
||||
private var displayId: Int = -1
|
||||
private var lensFacing: Int = CameraSelector.LENS_FACING_BACK
|
||||
private var preview: Preview? = null
|
||||
private var imageCapture: ImageCapture? = null
|
||||
private var camera: Camera? = null
|
||||
|
||||
/** Blocking camera operations are performed using this executor */
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// Make sure that all permissions are still present on resume,
|
||||
// since they could have been removed while away.
|
||||
if (!allPermissionsGranted()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
requireActivity(),
|
||||
REQUIRED_PERMISSIONS,
|
||||
REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
} else {
|
||||
//Bind the viewfinder here, since when leaving the fragment it gets unbound
|
||||
bindCameraUseCases()
|
||||
|
||||
// Build UI controls
|
||||
updateCameraUi()
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if all permission specified in the manifest have been granted
|
||||
*/
|
||||
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
|
||||
ContextCompat.checkSelfPermission(
|
||||
requireContext(), it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
|
||||
// Shut down our background executor
|
||||
cameraExecutor.shutdown()
|
||||
|
||||
// Unregister the broadcast receivers and listeners
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View? =
|
||||
inflater.inflate(R.layout.fragment_camera, container, false)
|
||||
|
||||
private fun setGalleryThumbnail(uri: String) {
|
||||
// Reference of the view that holds the gallery thumbnail
|
||||
val thumbnail = container.findViewById<ImageButton>(R.id.photo_view_button)
|
||||
|
||||
// Run the operations in the view's thread
|
||||
thumbnail.post {
|
||||
|
||||
// Remove thumbnail padding
|
||||
thumbnail.setPadding(10)
|
||||
|
||||
// Load thumbnail into circular button using Glide
|
||||
Glide.with(thumbnail)
|
||||
.load(uri)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
container = view as ConstraintLayout
|
||||
viewFinder = container.findViewById(R.id.view_finder)
|
||||
|
||||
// Initialize our background executor
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
|
||||
// Every time the orientation of device changes, update rotation for use cases
|
||||
|
||||
// Determine the output directory
|
||||
outputDirectory = getGalleryDirectory(requireContext())
|
||||
|
||||
// Wait for the views to be properly laid out
|
||||
viewFinder.post {
|
||||
|
||||
// Keep track of the display in which this view is attached
|
||||
displayId = viewFinder.display?.displayId ?: -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate camera controls and update the UI manually upon config changes to avoid removing
|
||||
* and re-adding the view finder from the view hierarchy; this provides a seamless rotation
|
||||
* transition on devices that support it.
|
||||
*
|
||||
* NOTE: The flag is supported starting in Android 8 but there still is a small flash on the
|
||||
* screen for devices that run Android 9 or below.
|
||||
*/
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
updateCameraUi()
|
||||
}
|
||||
|
||||
/** Declare and bind preview, capture and analysis use cases */
|
||||
private fun bindCameraUseCases() {
|
||||
|
||||
// Get screen metrics used to setup camera for full screen resolution
|
||||
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
|
||||
Log.d(TAG, "Screen metrics: ${metrics.widthPixels} x ${metrics.heightPixels}")
|
||||
|
||||
val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
|
||||
Log.d(TAG, "Preview aspect ratio: $screenAspectRatio")
|
||||
|
||||
val rotation = viewFinder.display.rotation
|
||||
|
||||
// Bind the CameraProvider to the LifeCycleOwner
|
||||
val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
|
||||
cameraProviderFuture.addListener(Runnable {
|
||||
|
||||
// CameraProvider
|
||||
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||
|
||||
// Preview
|
||||
preview = Preview.Builder()
|
||||
// We request aspect ratio but no resolution
|
||||
.setTargetAspectRatio(screenAspectRatio)
|
||||
// Set initial target rotation
|
||||
.setTargetRotation(rotation)
|
||||
.build()
|
||||
|
||||
// ImageCapture
|
||||
imageCapture = ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
|
||||
// We request aspect ratio but no resolution to match preview config, but letting
|
||||
// CameraX optimize for whatever specific resolution best fits our use cases
|
||||
.setTargetAspectRatio(screenAspectRatio)
|
||||
// Set initial target rotation, we will have to call this again if rotation changes
|
||||
// during the lifecycle of this use case
|
||||
.setTargetRotation(rotation)
|
||||
.build()
|
||||
|
||||
// Must unbind the use-cases before rebinding them
|
||||
cameraProvider.unbindAll()
|
||||
|
||||
try {
|
||||
// A variable number of use-cases can be passed here -
|
||||
// camera provides access to CameraControl & CameraInfo
|
||||
camera = cameraProvider.bindToLifecycle(
|
||||
this, cameraSelector, preview, imageCapture)
|
||||
|
||||
// Attach the viewfinder's surface provider to preview use case
|
||||
preview?.setSurfaceProvider(viewFinder.createSurfaceProvider(camera?.cameraInfo))
|
||||
} catch(exc: Exception) {
|
||||
Log.e(TAG, "Use case binding failed", exc)
|
||||
}
|
||||
|
||||
}, ContextCompat.getMainExecutor(requireContext()))
|
||||
}
|
||||
|
||||
/**
|
||||
* setTargetAspectRatio requires enum value of
|
||||
* [androidx.camera.core.AspectRatio]. Currently it has values of 4:3 & 16:9.
|
||||
*
|
||||
* Detecting the most suitable ratio for dimensions provided in @params by counting absolute
|
||||
* of preview ratio to one of the provided values.
|
||||
*
|
||||
* @param width - preview width
|
||||
* @param height - preview height
|
||||
* @return suitable aspect ratio
|
||||
*/
|
||||
private fun aspectRatio(width: Int, height: Int): Int {
|
||||
val previewRatio = max(width, height).toDouble() / min(width, height)
|
||||
if (abs(previewRatio - RATIO_4_3_VALUE) <= abs(previewRatio - RATIO_16_9_VALUE)) {
|
||||
return AspectRatio.RATIO_4_3
|
||||
}
|
||||
return AspectRatio.RATIO_16_9
|
||||
}
|
||||
|
||||
/** Method used to re-draw the camera UI controls, called every time configuration changes. */
|
||||
private fun updateCameraUi() {
|
||||
|
||||
// Remove previous UI if any
|
||||
container.findViewById<ConstraintLayout>(R.id.camera_ui_container)?.let {
|
||||
container.removeView(it)
|
||||
}
|
||||
|
||||
// Inflate a new view containing all UI for controlling the camera
|
||||
val controls = View.inflate(requireContext(), R.layout.camera_ui_container, container)
|
||||
|
||||
// In the background, load latest photo taken (if any) for gallery thumbnail
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
// Find the last picture
|
||||
// Find the last picture
|
||||
val projection = arrayOf(
|
||||
MediaStore.Images.ImageColumns._ID,
|
||||
MediaStore.Images.ImageColumns.DATA,
|
||||
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
|
||||
MediaStore.Images.ImageColumns.DATE_TAKEN,
|
||||
MediaStore.Images.ImageColumns.MIME_TYPE
|
||||
)
|
||||
val cursor = requireContext().contentResolver
|
||||
.query(
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, projection, null,
|
||||
null, MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC"
|
||||
)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val uri = Uri.parse(cursor.getString(1)).path ?: ""
|
||||
setGalleryThumbnail(uri)
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
setupImageCapture(controls)
|
||||
|
||||
setupFlipCameras(controls)
|
||||
|
||||
setupUploadImage(controls)
|
||||
}
|
||||
|
||||
private fun setupUploadImage(controls: View) {
|
||||
// Listener for button used to view the most recent photo
|
||||
controls.findViewById<ImageButton>(R.id.photo_view_button).setOnClickListener {
|
||||
Intent().apply {
|
||||
type = "image/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_LOCAL_ONLY, true)
|
||||
startActivityForResult(
|
||||
Intent.createChooser(this, "Select a Picture"), PICK_IMAGE_REQUEST
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFlipCameras(controls: View) {
|
||||
// Listener for button used to switch cameras
|
||||
controls.findViewById<ImageButton>(R.id.camera_switch_button).setOnClickListener {
|
||||
lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) {
|
||||
CameraSelector.LENS_FACING_BACK
|
||||
} else {
|
||||
CameraSelector.LENS_FACING_FRONT
|
||||
}
|
||||
// Re-bind use cases to update selected camera, being careful about permissions.
|
||||
if (!allPermissionsGranted()) {
|
||||
ActivityCompat.requestPermissions(
|
||||
requireActivity(),
|
||||
REQUIRED_PERMISSIONS,
|
||||
REQUEST_CODE_PERMISSIONS
|
||||
)
|
||||
} else {
|
||||
bindCameraUseCases()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupImageCapture(controls: View) {
|
||||
// Listener for button used to capture photo
|
||||
controls.findViewById<ImageButton>(R.id.camera_capture_button).setOnClickListener {
|
||||
|
||||
// Get a stable reference of the modifiable image capture use case
|
||||
imageCapture?.let { imageCapture ->
|
||||
|
||||
// Create output file to hold the image
|
||||
val photoFile = File.createTempFile(
|
||||
"${System.currentTimeMillis()}.jpg", null, context?.cacheDir
|
||||
)
|
||||
|
||||
// Setup image capture metadata
|
||||
val metadata = Metadata().apply {
|
||||
|
||||
// Mirror image when using the front camera
|
||||
isReversedHorizontal = lensFacing == CameraSelector.LENS_FACING_FRONT
|
||||
}
|
||||
|
||||
// Create output options object which contains file + metadata
|
||||
val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile)
|
||||
.setMetadata(metadata)
|
||||
.build()
|
||||
|
||||
// Setup image capture listener which is triggered after photo has been taken
|
||||
imageCapture.takePicture(
|
||||
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onError(exc: ImageCaptureException) {
|
||||
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
|
||||
}
|
||||
|
||||
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
|
||||
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
|
||||
startPostCreation(savedUri)
|
||||
}
|
||||
})
|
||||
|
||||
// Display flash animation to indicate that photo was captured
|
||||
container.postDelayed({
|
||||
container.foreground = ColorDrawable(Color.WHITE)
|
||||
container.postDelayed(
|
||||
{ container.foreground = null }, ANIMATION_FAST_MILLIS
|
||||
)
|
||||
}, ANIMATION_SLOW_MILLIS)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null
|
||||
&& (requestCode == PICK_IMAGE_REQUEST || requestCode == CAPTURE_IMAGE_REQUEST)
|
||||
&& data.data != null) {
|
||||
|
||||
startPostCreation(data.data!!)
|
||||
}
|
||||
}
|
||||
private fun startPostCreation(uri: Uri) {
|
||||
startActivity(
|
||||
Intent(activity, PhotoEditActivity::class.java)
|
||||
.putExtra("picture_uri", uri)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "CameraFragment"
|
||||
private const val RATIO_4_3_VALUE = 4.0 / 3.0
|
||||
private const val RATIO_16_9_VALUE = 16.0 / 9.0
|
||||
|
||||
/** Use external media if it is available, our app's file directory otherwise */
|
||||
private fun getGalleryDirectory(context: Context): File {
|
||||
val appContext = context.applicationContext
|
||||
val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
|
||||
File(it, appContext.resources.getString(R.string.app_name)).apply { mkdirs() } }
|
||||
return if (mediaDir != null && mediaDir.exists())
|
||||
mediaDir else appContext.filesDir
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -17,9 +17,12 @@ class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
private lateinit var seekbarSaturation: SeekBar
|
||||
private lateinit var seekbarContrast: SeekBar
|
||||
|
||||
private var BRIGHTNESS_START = 100
|
||||
private var SATURATION_START = 0
|
||||
private var CONTRAST_START = 10
|
||||
private var BRIGHTNESS_MAX = 200
|
||||
private var SATURATION_MAX = 20
|
||||
private var CONTRAST_MAX= 30
|
||||
private var BRIGHTNESS_START = BRIGHTNESS_MAX/2
|
||||
private var SATURATION_START = SATURATION_MAX/2
|
||||
private var CONTRAST_START = CONTRAST_MAX/2
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
|
@ -32,13 +35,13 @@ class EditImageFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
|
|||
seekbarSaturation = view.findViewById(R.id.seekbar_saturation)
|
||||
seekbarContrast = view.findViewById(R.id.seekbar_contrast)
|
||||
|
||||
seekbarBrightness.max = 200
|
||||
seekbarBrightness.max = BRIGHTNESS_MAX
|
||||
seekbarBrightness.progress = BRIGHTNESS_START
|
||||
|
||||
seekbarContrast.max = 20
|
||||
seekbarContrast.max = CONTRAST_MAX
|
||||
seekbarContrast.progress = CONTRAST_START
|
||||
|
||||
seekbarSaturation.max = 30
|
||||
seekbarSaturation.max = SATURATION_MAX
|
||||
seekbarSaturation.progress = SATURATION_START
|
||||
|
||||
seekbarBrightness.setOnSeekBarChangeListener(this)
|
||||
|
|
|
@ -53,15 +53,12 @@ class FilterListFragment : Fragment(), FilterListFragmentListener {
|
|||
|
||||
fun displayImage(bitmap: Bitmap?) {
|
||||
val r = Runnable {
|
||||
val tbImage: Bitmap?
|
||||
if (bitmap == null) {
|
||||
tbImage = MediaStore.Images.Media.getBitmap(requireActivity().contentResolver, PhotoEditActivity.URI.picture_uri)
|
||||
val tbImage: Bitmap = (if (bitmap == null) {
|
||||
MediaStore.Images.Media.getBitmap(requireActivity().contentResolver, PhotoEditActivity.URI.picture_uri)
|
||||
} else {
|
||||
tbImage = Bitmap.createScaledBitmap(bitmap, 100, 100, false)
|
||||
}
|
||||
|
||||
if (tbImage == null)
|
||||
return@Runnable
|
||||
Bitmap.createScaledBitmap(bitmap, 100, 100, false)
|
||||
})
|
||||
?: return@Runnable
|
||||
|
||||
setupFilter(tbImage)
|
||||
|
||||
|
@ -78,7 +75,7 @@ class FilterListFragment : Fragment(), FilterListFragmentListener {
|
|||
|
||||
val tbItem = ThumbnailItem()
|
||||
tbItem.image = tbImage
|
||||
tbItem.filterName = "Normal"
|
||||
tbItem.filterName = getString(R.string.normal_filter)
|
||||
ThumbnailsManager.addThumb(tbItem)
|
||||
|
||||
val filters = FilterPack.getFilterPack(requireActivity())
|
||||
|
|
|
@ -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"
|
||||
|
@ -58,7 +53,9 @@ class ImageFragment : Fragment() {
|
|||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(object: BasePermissionListener() {
|
||||
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
|
||||
Toast.makeText(view.context, "You need to grant write permission to download pictures!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(view.context,
|
||||
view.context.getString(R.string.write_permission_download_pic),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
|
||||
|
@ -72,7 +69,9 @@ class ImageFragment : Fragment() {
|
|||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(object: BasePermissionListener() {
|
||||
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
|
||||
Toast.makeText(view.context, "You need to grant write permission to share pictures!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(view.context,
|
||||
view.context.getString(R.string.write_permission_share_pic),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
|
||||
|
@ -96,13 +95,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 {
|
|
@ -1,67 +0,0 @@
|
|||
package com.h.pixeldroid.fragments
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.h.pixeldroid.PhotoEditActivity
|
||||
import com.h.pixeldroid.R
|
||||
|
||||
/**
|
||||
* This fragment is the entry point to create a post.
|
||||
* You can either upload an existing picture or take a new one.
|
||||
* once the URI of the picture to be posted is set, it will send
|
||||
* it to the post creation activity where you can modify it,
|
||||
* add a description and more.
|
||||
*/
|
||||
|
||||
class NewPostFragment : Fragment() {
|
||||
|
||||
private val PICK_IMAGE_REQUEST = 1
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_new_post, container, false)
|
||||
|
||||
val uploadPictureButton: Button = view.findViewById(R.id.uploadPictureButton)
|
||||
uploadPictureButton.setOnClickListener{
|
||||
uploadPicture()
|
||||
}
|
||||
|
||||
val takePictureButton: Button = view.findViewById(R.id.takePictureButton)
|
||||
takePictureButton.setOnClickListener{
|
||||
val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index")
|
||||
val intent = Intent(context, PhotoEditActivity::class.java).putExtra("uri", uri)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null
|
||||
&& requestCode == PICK_IMAGE_REQUEST && data.data != null)
|
||||
startActivity(Intent(activity, PhotoEditActivity::class.java)
|
||||
.putExtra("uri", data.data)
|
||||
)
|
||||
}
|
||||
|
||||
private fun uploadPicture() {
|
||||
Intent().apply {
|
||||
type = "image/*"
|
||||
action = Intent.ACTION_GET_CONTENT
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
putExtra(Intent.EXTRA_LOCAL_ONLY, true)
|
||||
startActivityForResult(
|
||||
Intent.createChooser(this, "Select a Picture"), PICK_IMAGE_REQUEST
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package com.h.pixeldroid.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
|
@ -9,13 +8,13 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.bumptech.glide.Glide
|
||||
import com.h.pixeldroid.BuildConfig
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||
import com.h.pixeldroid.objects.Status
|
||||
import com.h.pixeldroid.objects.Status.Companion.DOMAIN_TAG
|
||||
import com.h.pixeldroid.objects.Status.Companion.POST_TAG
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
|
||||
|
||||
class PostFragment : Fragment() {
|
||||
|
@ -25,29 +24,30 @@ class PostFragment : Fragment() {
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val current_status = arguments?.getSerializable(POST_TAG) as Status?
|
||||
val domain = arguments?.getString(DOMAIN_TAG)!!
|
||||
val statusDomain = arguments?.getString(DOMAIN_TAG)!!
|
||||
val root: View = inflater.inflate(R.layout.post_fragment, container, false)
|
||||
val picRequest = Glide.with(this)
|
||||
.asDrawable().fitCenter()
|
||||
.placeholder(ColorDrawable(Color.GRAY))
|
||||
|
||||
current_status?.setupPost(root, picRequest, this, domain, true)
|
||||
current_status?.setupPost(root, picRequest, this, statusDomain, true)
|
||||
|
||||
//Setup arguments needed for the onclicklisteners
|
||||
val holder = PostViewHolder(root, requireContext())
|
||||
val db = DBUtils.initDB(requireContext())
|
||||
|
||||
val preferences = requireActivity().getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
val accessToken = preferences.getString("accessToken", "")
|
||||
val api = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
val user = db.userDao().getActiveUser()
|
||||
|
||||
val domain = user?.instance_uri.orEmpty()
|
||||
val accessToken = user?.accessToken.orEmpty()
|
||||
val api = PixelfedAPI.create(domain)
|
||||
|
||||
current_status?.setDescription(root, api, "Bearer $accessToken")
|
||||
|
||||
//Activate onclickListeners
|
||||
current_status?.activateBookmarker(holder, api, "Bearer $accessToken", current_status.bookmarked)
|
||||
current_status?.activateLiker(holder, api, "Bearer $accessToken", current_status!!.favourited)
|
||||
current_status?.activateReblogger(holder, api, "Bearer $accessToken", current_status!!.reblogged)
|
||||
current_status?.activateLiker(holder, api, "Bearer $accessToken", current_status.favourited)
|
||||
current_status?.activateReblogger(holder, api, "Bearer $accessToken", current_status.reblogged)
|
||||
current_status?.activateCommenter(holder, api, "Bearer $accessToken")
|
||||
current_status?.showComments(holder, api, "Bearer $accessToken")
|
||||
|
||||
|
|
|
@ -13,11 +13,13 @@ import com.h.pixeldroid.adapters.ProfilePostsRecyclerViewAdapter
|
|||
|
||||
|
||||
/**
|
||||
<<<<<<< HEAD:app/src/main/java/com/h/pixeldroid/fragments/ProfilePostGridFragment.kt
|
||||
* A fragment representing a list of Items.
|
||||
* Activities containing this fragment MUST implement the
|
||||
* [ProfilePostGridFragment.OnListFragmentInteractionListener] interface.
|
||||
*/
|
||||
class ProfilePostGridFragment : Fragment() {
|
||||
|
||||
private var columnCount = 3
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.h.pixeldroid.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
|
@ -16,7 +14,6 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.h.pixeldroid.BuildConfig
|
||||
import com.h.pixeldroid.PostActivity
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.SearchActivity
|
||||
|
@ -24,6 +21,7 @@ import com.h.pixeldroid.api.PixelfedAPI
|
|||
import com.h.pixeldroid.objects.DiscoverPost
|
||||
import com.h.pixeldroid.objects.DiscoverPosts
|
||||
import com.h.pixeldroid.objects.Status
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
|
@ -35,7 +33,6 @@ import retrofit2.Response
|
|||
|
||||
class SearchDiscoverFragment : Fragment() {
|
||||
private lateinit var api: PixelfedAPI
|
||||
private lateinit var preferences: SharedPreferences
|
||||
private lateinit var recycler : RecyclerView
|
||||
private lateinit var adapter : DiscoverRecyclerViewAdapter
|
||||
private lateinit var accessToken: String
|
||||
|
@ -43,7 +40,6 @@ class SearchDiscoverFragment : Fragment() {
|
|||
private lateinit var discoverRefreshLayout: SwipeRefreshLayout
|
||||
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
|
@ -67,11 +63,14 @@ class SearchDiscoverFragment : Fragment() {
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
preferences = requireActivity().getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
api = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
accessToken = preferences.getString("accessToken", "") ?: ""
|
||||
|
||||
val db = DBUtils.initDB(requireContext())
|
||||
|
||||
val user = db.userDao().getActiveUser()
|
||||
|
||||
val domain = user?.instance_uri.orEmpty()
|
||||
api = PixelfedAPI.create(domain)
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
|
||||
discoverProgressBar = view.findViewById(R.id.discoverProgressBar)
|
||||
discoverRefreshLayout = view.findViewById(R.id.discoverRefreshLayout)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package com.h.pixeldroid.fragments.feeds
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
|
@ -20,10 +19,12 @@ import androidx.recyclerview.widget.DiffUtil
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.h.pixeldroid.BuildConfig
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.objects.FeedContent
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import kotlinx.android.synthetic.main.fragment_feed.view.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
|
@ -36,12 +37,15 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
|
|||
|
||||
protected var accessToken: String? = null
|
||||
protected lateinit var pixelfedAPI: PixelfedAPI
|
||||
protected lateinit var preferences: SharedPreferences
|
||||
|
||||
protected lateinit var list : RecyclerView
|
||||
protected lateinit var adapter : FeedsRecyclerViewAdapter<T, VH>
|
||||
protected lateinit var swipeRefreshLayout: SwipeRefreshLayout
|
||||
internal lateinit var loadingIndicator: ProgressBar
|
||||
private var user: UserDatabaseEntity? = null
|
||||
private lateinit var db: AppDatabase
|
||||
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -54,12 +58,11 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
|
|||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout)
|
||||
loadingIndicator = view.findViewById(R.id.progressBar)
|
||||
list = swipeRefreshLayout.list
|
||||
preferences = requireActivity().getSharedPreferences(
|
||||
"${BuildConfig.APPLICATION_ID}.pref", Context.MODE_PRIVATE
|
||||
)
|
||||
list.layoutManager = LinearLayoutManager(context)
|
||||
pixelfedAPI = PixelfedAPI.create("${preferences.getString("domain", "")}")
|
||||
accessToken = preferences.getString("accessToken", "")
|
||||
db = DBUtils.initDB(requireContext())
|
||||
user = db.userDao().getActiveUser()
|
||||
pixelfedAPI = PixelfedAPI.create(user?.instance_uri.orEmpty())
|
||||
accessToken = user?.accessToken.orEmpty()
|
||||
|
||||
return view
|
||||
}
|
||||
|
@ -113,14 +116,14 @@ open class FeedFragment<T: FeedContent, VH: RecyclerView.ViewHolder?>: Fragment(
|
|||
callback.onResult(notifications as List<T>)
|
||||
|
||||
} else{
|
||||
Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
loadingIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<List<T>>, t: Throwable) {
|
||||
Toast.makeText(context,"Could not get feed", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, getString(R.string.feed_failed), Toast.LENGTH_SHORT).show()
|
||||
Log.e("FeedFragment", t.toString())
|
||||
}
|
||||
})
|
||||
|
@ -149,7 +152,7 @@ abstract class FeedsRecyclerViewAdapter<T: FeedContent, VH : RecyclerView.ViewHo
|
|||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean {
|
||||
return oldItem == newItem
|
||||
return oldItem.equals(newItem)
|
||||
}
|
||||
}
|
||||
){
|
||||
|
|
|
@ -9,7 +9,6 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
|
@ -81,7 +80,7 @@ class NotificationsFragment : FeedFragment<Notification, NotificationsFragment.N
|
|||
private fun makeContent(): LiveData<PagedList<Notification>> {
|
||||
fun makeInitialCall(requestedLoadSize: Int): Call<List<Notification>> {
|
||||
return pixelfedAPI
|
||||
.notifications("Bearer $accessToken", min_id="1", limit="$requestedLoadSize")
|
||||
.notifications("Bearer $accessToken", limit="$requestedLoadSize")
|
||||
}
|
||||
fun makeAfterCall(requestedLoadSize: Int, key: String): Call<List<Notification>> {
|
||||
return pixelfedAPI
|
||||
|
|
|
@ -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
|
||||
|
@ -21,20 +20,26 @@ import com.bumptech.glide.RequestBuilder
|
|||
import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader
|
||||
import com.bumptech.glide.util.ViewPreloadSizeProvider
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.objects.Status
|
||||
import com.h.pixeldroid.utils.DBUtils
|
||||
import retrofit2.Call
|
||||
|
||||
open class PostsFeedFragment : FeedFragment<Status, PostViewHolder>() {
|
||||
|
||||
lateinit var picRequest: RequestBuilder<Drawable>
|
||||
lateinit var domain : String
|
||||
private var user: UserDatabaseEntity? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
||||
domain = preferences.getString("domain", "")!!
|
||||
val db = DBUtils.initDB(requireContext())
|
||||
user = db.userDao().getActiveUser()
|
||||
|
||||
domain = user?.instance_uri.orEmpty()
|
||||
//RequestBuilder that is re-used for every image
|
||||
picRequest = Glide.with(this)
|
||||
.asDrawable().fitCenter()
|
||||
|
|
|
@ -8,8 +8,7 @@ import retrofit2.Call
|
|||
|
||||
class PublicTimelineFragment: PostsFeedFragment() {
|
||||
|
||||
inner class SearchFeedDataSource(
|
||||
) : FeedDataSource(null, null){
|
||||
inner class SearchFeedDataSource : FeedDataSource(null, null){
|
||||
|
||||
override fun newSource(): FeedDataSource {
|
||||
return SearchFeedDataSource()
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.widget.Toast
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.fragments.feeds.AccountListFragment
|
||||
import com.h.pixeldroid.fragments.feeds.FeedFragment
|
||||
import com.h.pixeldroid.objects.Account
|
||||
|
@ -72,14 +73,14 @@ class SearchAccountFragment: AccountListFragment(){
|
|||
callback.onResult(notifications as List<Account>)
|
||||
|
||||
} else{
|
||||
Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, getString(R.string.loading_toast), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
loadingIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Results>, t: Throwable) {
|
||||
Toast.makeText(context,"Could not get feed", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context,getString(R.string.feed_failed), Toast.LENGTH_SHORT).show()
|
||||
Log.e("FeedFragment", t.toString())
|
||||
}
|
||||
})
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.LiveData
|
||||
|
@ -14,21 +13,11 @@ import androidx.lifecycle.Observer
|
|||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.ListPreloader
|
||||
import com.bumptech.glide.RequestBuilder
|
||||
import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader
|
||||
import com.bumptech.glide.util.ViewPreloadSizeProvider
|
||||
import com.h.pixeldroid.R
|
||||
import com.h.pixeldroid.fragments.feeds.AccountListFragment
|
||||
import com.h.pixeldroid.fragments.feeds.FeedFragment
|
||||
import com.h.pixeldroid.fragments.feeds.FeedsRecyclerViewAdapter
|
||||
import com.h.pixeldroid.fragments.feeds.NotificationsFragment
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Notification
|
||||
import com.h.pixeldroid.objects.Results
|
||||
import com.h.pixeldroid.objects.Tag
|
||||
import kotlinx.android.synthetic.main.account_list_entry.view.*
|
||||
import kotlinx.android.synthetic.main.fragment_tags.view.*
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
|
@ -110,14 +99,14 @@ class SearchHashtagFragment: FeedFragment<Tag, SearchHashtagFragment.TagsRecycle
|
|||
callback.onResult(notifications as List<Tag>)
|
||||
|
||||
} else{
|
||||
Toast.makeText(context,"Something went wrong while loading", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context,getString(R.string.loading_toast), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
swipeRefreshLayout.isRefreshing = false
|
||||
loadingIndicator.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Results>, t: Throwable) {
|
||||
Toast.makeText(context,"Could not get feed", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context,getString(R.string.feed_failed), Toast.LENGTH_SHORT).show()
|
||||
Log.e("FeedFragment", t.toString())
|
||||
}
|
||||
})
|
||||
|
|
|
@ -5,7 +5,6 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
|
@ -34,8 +33,7 @@ class SearchPostsFragment: PostsFeedFragment(){
|
|||
return view
|
||||
}
|
||||
|
||||
inner class SearchFeedDataSource(
|
||||
) : FeedDataSource(null, null){
|
||||
inner class SearchFeedDataSource : FeedDataSource(null, null){
|
||||
|
||||
override fun newSource(): FeedDataSource {
|
||||
return SearchFeedDataSource()
|
||||
|
|
|
@ -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,6 @@ package com.h.pixeldroid.objects
|
|||
abstract class FeedContent {
|
||||
abstract val id: String
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return super.equals(other)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@ import java.io.Serializable
|
|||
class Field : Serializable {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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,6 +2,4 @@ package com.h.pixeldroid.objects
|
|||
|
||||
import java.io.Serializable
|
||||
|
||||
class Poll : Serializable {
|
||||
|
||||
}
|
||||
class Poll : Serializable
|
||||
|
|
|
@ -5,3 +5,4 @@ import java.io.Serializable
|
|||
class Source : Serializable {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,37 +2,31 @@ package com.h.pixeldroid.objects
|
|||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.ColorMatrixColorFilter
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Spanned
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.*
|
||||
import androidx.core.text.toSpanned
|
||||
import android.widget.TextView
|
||||
import android.widget.LinearLayout
|
||||
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.R
|
||||
import com.h.pixeldroid.api.PixelfedAPI
|
||||
import com.h.pixeldroid.fragments.ImageFragment
|
||||
import com.h.pixeldroid.fragments.feeds.PostViewHolder
|
||||
import com.h.pixeldroid.utils.HtmlUtils.Companion.getDomain
|
||||
import com.h.pixeldroid.utils.HtmlUtils.Companion.parseHTMLText
|
||||
import com.h.pixeldroid.utils.ImageConverter
|
||||
import com.h.pixeldroid.utils.ImageUtils.Companion.downloadImage
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.bookmarkPostCall
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.censorColorMatrix
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.likePostCall
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.postComment
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.reblogPost
|
||||
|
@ -40,25 +34,18 @@ import com.h.pixeldroid.utils.PostUtils.Companion.retrieveComments
|
|||
import com.h.pixeldroid.utils.PostUtils.Companion.toggleCommentInput
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.unBookmarkPostCall
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.unLikePostCall
|
||||
import com.h.pixeldroid.utils.PostUtils.Companion.uncensorColorMatrix
|
||||
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 kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
import java.io.Serializable
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postPager
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postPicture
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.postTabs
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.profilePic
|
||||
|
||||
/*
|
||||
Represents a status posted by an account.
|
||||
|
@ -99,13 +86,11 @@ data class Status(
|
|||
val muted: Boolean = false,
|
||||
val bookmarked: Boolean = false,
|
||||
val pinned: Boolean = false
|
||||
) : Serializable, FeedContent()
|
||||
) : Serializable, FeedContent()
|
||||
{
|
||||
|
||||
companion object {
|
||||
const val SAVE_TO_GALLERY_WRITE_PERMISSION = 1
|
||||
const val POST_TAG = "postTag"
|
||||
const val POST_FRAG_TAG = "postFragTag"
|
||||
const val DOMAIN_TAG = "domainTag"
|
||||
const val DISCOVER_TAG = "discoverTag"
|
||||
}
|
||||
|
@ -120,31 +105,24 @@ data class Status(
|
|||
private fun getDescription(api: PixelfedAPI, context: Context, credential: String) : Spanned {
|
||||
val description = content
|
||||
if(description.isEmpty()) {
|
||||
return "No description".toSpanned()
|
||||
return context.getString(R.string.no_description).toSpanned()
|
||||
}
|
||||
return parseHTMLText(description, mentions, api, context, credential)
|
||||
|
||||
}
|
||||
|
||||
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(context: Context) : CharSequence {
|
||||
return context.getString(R.string.likes).format(favourites_count.toString())
|
||||
}
|
||||
|
||||
fun getNLikes() : CharSequence {
|
||||
val nLikes = favourites_count
|
||||
return "$nLikes Likes"
|
||||
fun getNShares(context: Context) : CharSequence {
|
||||
return context.getString(R.string.shares).format(reblogs_count.toString())
|
||||
}
|
||||
|
||||
fun getNShares() : CharSequence {
|
||||
val nShares = reblogs_count
|
||||
return "$nShares Shares"
|
||||
}
|
||||
|
||||
private fun ISO8601toDate(dateString : String, textView: TextView, isActivity: Boolean) {
|
||||
private fun ISO8601toDate(dateString : String, textView: TextView, isActivity: Boolean, context: Context) {
|
||||
var format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'")
|
||||
if(dateString.matches("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{6}Z".toRegex())) {
|
||||
format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.hhmmss'Z'")
|
||||
|
@ -160,8 +138,10 @@ data class Status(
|
|||
.getRelativeTimeSpanString(then, now,
|
||||
android.text.format.DateUtils.SECOND_IN_MILLIS,
|
||||
android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE)
|
||||
textView.text = if(isActivity) "Posted on $date"
|
||||
|
||||
textView.text = if(isActivity) context.getString(R.string.posted_on).format(date)
|
||||
else "$formattedDate"
|
||||
|
||||
} catch (e: ParseException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
@ -175,28 +155,47 @@ data class Status(
|
|||
}
|
||||
|
||||
private fun setupPostPics(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
|
||||
//Check whether or not we need to activate the viewPager
|
||||
if(media_attachments?.size == 1) {
|
||||
rootView.postPicture.visibility = VISIBLE
|
||||
rootView.postPager.visibility = GONE
|
||||
rootView.postTabs.visibility = GONE
|
||||
|
||||
// Standard layout
|
||||
rootView.postPicture.visibility = VISIBLE
|
||||
rootView.postPager.visibility = GONE
|
||||
rootView.postTabs.visibility = GONE
|
||||
|
||||
if (sensitive) {
|
||||
setupSensitiveLayout(rootView, request, homeFragment)
|
||||
request.load(this.getPostUrl()).into(rootView.postPicture)
|
||||
} else if(media_attachments?.size!! > 1) {
|
||||
//Only show the viewPager and tabs
|
||||
rootView.postPicture.visibility = GONE
|
||||
rootView.postPager.visibility = VISIBLE
|
||||
rootView.postTabs.visibility = VISIBLE
|
||||
|
||||
val tabs : ArrayList<ImageFragment> = ArrayList()
|
||||
} else {
|
||||
rootView.sensitiveWarning.visibility = GONE
|
||||
|
||||
//Fill the tabs with each mediaAttachment
|
||||
for(media in media_attachments) {
|
||||
tabs.add(ImageFragment.newInstance(media.url))
|
||||
if(media_attachments?.size == 1) {
|
||||
request.load(this.getPostUrl()).into(rootView.postPicture)
|
||||
|
||||
} else if(media_attachments?.size!! > 1) {
|
||||
setupTabsLayout(rootView, request, homeFragment)
|
||||
}
|
||||
setupTabs(tabs, rootView, homeFragment)
|
||||
|
||||
imagePopUpMenu(rootView, homeFragment.requireActivity())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabsLayout(rootView: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
|
||||
//Only show the viewPager and tabs
|
||||
rootView.postPicture.visibility = GONE
|
||||
rootView.postPager.visibility = VISIBLE
|
||||
rootView.postTabs.visibility = VISIBLE
|
||||
|
||||
val tabs : ArrayList<ImageFragment> = ArrayList()
|
||||
|
||||
//Fill the tabs with each mediaAttachment
|
||||
for(media in media_attachments!!) {
|
||||
tabs.add(ImageFragment.newInstance(media.url))
|
||||
}
|
||||
|
||||
setupTabs(tabs, rootView, homeFragment)
|
||||
}
|
||||
|
||||
|
||||
private fun setupTabs(tabs: ArrayList<ImageFragment>, rootView: View, homeFragment: Fragment) {
|
||||
//Attach the given tabs to the view pager
|
||||
rootView.postPager.adapter = object : FragmentStateAdapter(homeFragment) {
|
||||
|
@ -208,6 +207,7 @@ data class Status(
|
|||
return media_attachments?.size ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
TabLayoutMediator(rootView.postTabs, rootView.postPager) { tab, _ ->
|
||||
tab.icon = rootView.context.getDrawable(R.drawable.ic_dot_blue_12dp)
|
||||
}.attach()
|
||||
|
@ -221,25 +221,29 @@ data class Status(
|
|||
isActivity : Boolean
|
||||
) {
|
||||
//Setup username as a button that opens the profile
|
||||
val username = rootView.findViewById<TextView>(R.id.username)
|
||||
username.text = this.getUsername()
|
||||
username.setTypeface(null, Typeface.BOLD)
|
||||
username.setOnClickListener { account.openProfile(rootView.context) }
|
||||
rootView.findViewById<TextView>(R.id.username).apply {
|
||||
text = this@Status.getUsername()
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
setOnClickListener { account.openProfile(rootView.context) }
|
||||
}
|
||||
|
||||
val usernameDesc = rootView.findViewById<TextView>(R.id.usernameDesc)
|
||||
usernameDesc.text = this.getUsername()
|
||||
usernameDesc.setTypeface(null, Typeface.BOLD)
|
||||
rootView.findViewById<TextView>(R.id.usernameDesc).apply {
|
||||
text = this@Status.getUsername()
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
val nlikes = rootView.findViewById<TextView>(R.id.nlikes)
|
||||
nlikes.text = this.getNLikes()
|
||||
nlikes.setTypeface(null, Typeface.BOLD)
|
||||
rootView.findViewById<TextView>(R.id.nlikes).apply {
|
||||
text = this@Status.getNLikes(rootView.context)
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
val nshares = rootView.findViewById<TextView>(R.id.nshares)
|
||||
nshares.text = this.getNShares()
|
||||
nshares.setTypeface(null, Typeface.BOLD)
|
||||
rootView.findViewById<TextView>(R.id.nshares).apply {
|
||||
text = this@Status.getNShares(rootView.context)
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
}
|
||||
|
||||
//Convert the date to a readable string
|
||||
ISO8601toDate(created_at, rootView.postDate, isActivity)
|
||||
ISO8601toDate(created_at, rootView.postDate, isActivity, rootView.context)
|
||||
|
||||
rootView.postDomain.text = getStatusDomain(domain)
|
||||
|
||||
|
@ -258,16 +262,16 @@ data class Status(
|
|||
|
||||
|
||||
//Set comment initial visibility
|
||||
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = View.GONE
|
||||
|
||||
imagePopUpMenu(rootView, homeFragment.requireActivity())
|
||||
rootView.findViewById<LinearLayout>(R.id.commentIn).visibility = GONE
|
||||
}
|
||||
|
||||
fun setDescription(rootView: View, api : PixelfedAPI, credential: String) {
|
||||
val desc = rootView.findViewById<TextView>(R.id.description)
|
||||
|
||||
desc.text = this.getDescription(api, rootView.context, credential)
|
||||
desc.movementMethod = LinkMovementMethod.getInstance()
|
||||
desc.apply {
|
||||
text = this@Status.getDescription(api, rootView.context, credential)
|
||||
movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
fun activateReblogger(
|
||||
|
@ -276,19 +280,21 @@ data class Status(
|
|||
credential: String,
|
||||
isReblogged : Boolean
|
||||
) {
|
||||
//Set initial button state
|
||||
holder.reblogger.isChecked = isReblogged
|
||||
holder.reblogger.apply {
|
||||
//Set initial button state
|
||||
isChecked = isReblogged
|
||||
|
||||
//Activate the button
|
||||
holder.reblogger.setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
Log.e("REBLOG", "Reblogged post")
|
||||
// Button is active
|
||||
reblogPost(holder, api, credential, this)
|
||||
} else {
|
||||
Log.e("REBLOG", "Undo Reblogged post")
|
||||
// Button is inactive
|
||||
undoReblogPost(holder, api, credential, this)
|
||||
//Activate the button
|
||||
setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
// Button is active
|
||||
undoReblogPost(holder, api, credential, this@Status)
|
||||
} else {
|
||||
// Button is inactive
|
||||
reblogPost(holder, api, credential, this@Status)
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,20 +305,25 @@ data class Status(
|
|||
credential: String,
|
||||
isLiked: Boolean
|
||||
) {
|
||||
//Set initial state
|
||||
holder.liker.isChecked = isLiked
|
||||
|
||||
//Activate the liker
|
||||
holder.liker.setEventListener { _, buttonState ->
|
||||
holder.liker.apply {
|
||||
//Set initial state
|
||||
isChecked = isLiked
|
||||
|
||||
//Activate the liker
|
||||
setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
// Button is active
|
||||
likePostCall(holder, api, credential, this)
|
||||
// Button is active, unlike
|
||||
unLikePostCall(holder, api, credential, this@Status)
|
||||
} else {
|
||||
// Button is inactive
|
||||
unLikePostCall(holder, api, credential, this)
|
||||
// Button is inactive, like
|
||||
likePostCall(holder, api, credential, this@Status)
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun activateBookmarker(
|
||||
holder : PostViewHolder,
|
||||
|
@ -321,18 +332,20 @@ data class Status(
|
|||
isBookmarked: Boolean
|
||||
) {
|
||||
// Set initial state
|
||||
holder.bookmarker.isChecked = isBookmarked
|
||||
holder.bookmarker.apply {
|
||||
isChecked = isBookmarked
|
||||
|
||||
// Activate bookmarker
|
||||
holder.bookmarker.setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
Log.e("BUTTON ACTIVE", buttonState.toString())
|
||||
// Button is active
|
||||
bookmarkPostCall(holder, api, credential, this)
|
||||
} else {
|
||||
Log.e("BUTTON INACTIVE", buttonState.toString())
|
||||
// Button is inactive
|
||||
unBookmarkPostCall(holder, api, credential, this)
|
||||
// Activate bookmarker
|
||||
setEventListener { _, buttonState ->
|
||||
if (buttonState) {
|
||||
// Button is active
|
||||
bookmarkPostCall(holder, api, credential, this@Status)
|
||||
} else {
|
||||
// Button is inactive
|
||||
unBookmarkPostCall(holder, api, credential, this@Status)
|
||||
}
|
||||
//show animation or not?
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -345,14 +358,16 @@ data class Status(
|
|||
) {
|
||||
//Show all comments of a post
|
||||
if (replies_count == 0) {
|
||||
holder.viewComment.text = "No comments on this post..."
|
||||
holder.viewComment.text = holder.context.getString(R.string.NoCommentsToShow)
|
||||
} else {
|
||||
holder.viewComment.text = "View all $replies_count comments..."
|
||||
holder.viewComment.setOnClickListener {
|
||||
holder.viewComment.visibility = View.GONE
|
||||
holder.viewComment.apply {
|
||||
text = "$replies_count ${holder.context.getString(R.string.CommentDisplay)}"
|
||||
setOnClickListener {
|
||||
visibility = GONE
|
||||
|
||||
//Retrieve the comments
|
||||
retrieveComments(holder, api, credential, this)
|
||||
//Retrieve the comments
|
||||
retrieveComments(holder, api, credential, this@Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -370,7 +385,7 @@ data class Status(
|
|||
val textIn = holder.comment.text
|
||||
//Open text input
|
||||
if(textIn.isNullOrEmpty()) {
|
||||
Toast.makeText(holder.context,"Comment must not be empty!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(holder.context, holder.context.getString(R.string.empty_comment), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
|
||||
//Post the comment
|
||||
|
@ -384,7 +399,7 @@ data class Status(
|
|||
}
|
||||
|
||||
|
||||
fun imagePopUpMenu(view: View, activity: FragmentActivity) {
|
||||
private fun imagePopUpMenu(view: View, activity: FragmentActivity) {
|
||||
val anchor = view.findViewById<FrameLayout>(R.id.post_fragment_image_popup_menu_anchor)
|
||||
if (!media_attachments.isNullOrEmpty() && media_attachments.size == 1) {
|
||||
view.findViewById<ImageView>(R.id.postPicture).setOnLongClickListener {
|
||||
|
@ -396,7 +411,7 @@ data class Status(
|
|||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(object: BasePermissionListener() {
|
||||
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
|
||||
Toast.makeText(view.context, "You need to grant write permission to download pictures!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(view.context, view.context.getString(R.string.write_permission_download_pic), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
|
||||
|
@ -410,7 +425,7 @@ data class Status(
|
|||
.withPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(object: BasePermissionListener() {
|
||||
override fun onPermissionDenied(p0: PermissionDeniedResponse?) {
|
||||
Toast.makeText(view.context, "You need to grant write permission to share pictures!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(view.context, view.context.getString(R.string.write_permission_share_pic), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onPermissionGranted(p0: PermissionGrantedResponse?) {
|
||||
|
@ -429,4 +444,31 @@ data class Status(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSensitiveLayout(view: View, request: RequestBuilder<Drawable>, homeFragment: Fragment) {
|
||||
|
||||
// Set dark layout and warning message
|
||||
view.sensitiveWarning.visibility = VISIBLE
|
||||
view.postPicture.colorFilter = ColorMatrixColorFilter(censorColorMatrix())
|
||||
|
||||
fun uncensorPicture(view: View) {
|
||||
if (!media_attachments.isNullOrEmpty()) {
|
||||
view.sensitiveWarning.visibility = GONE
|
||||
view.postPicture.colorFilter = ColorMatrixColorFilter(uncensorColorMatrix())
|
||||
|
||||
if (media_attachments.size > 1)
|
||||
setupTabsLayout(view, request, homeFragment)
|
||||
}
|
||||
imagePopUpMenu(view, homeFragment.requireActivity())
|
||||
}
|
||||
|
||||
|
||||
view.findViewById<TextView>(R.id.sensitiveWarning).setOnClickListener {
|
||||
uncensorPicture(view)
|
||||
}
|
||||
|
||||
view.findViewById<ImageView>(R.id.postPicture).setOnClickListener {
|
||||
uncensorPicture(view)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
import com.h.pixeldroid.db.AppDatabase
|
||||
import com.h.pixeldroid.db.InstanceDatabaseEntity
|
||||
import com.h.pixeldroid.db.UserDatabaseEntity
|
||||
import com.h.pixeldroid.objects.Account
|
||||
import com.h.pixeldroid.objects.Instance
|
||||
import com.h.pixeldroid.utils.Utils.Companion.normalizeDomain
|
||||
|
||||
class DBUtils {
|
||||
companion object {
|
||||
fun initDB(context: Context): AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
context,
|
||||
AppDatabase::class.java, "pixeldroid"
|
||||
).allowMainThreadQueries().build()
|
||||
}
|
||||
private fun normalizeOrNot(uri: String): String{
|
||||
return if(uri.startsWith("http://localhost")){
|
||||
uri
|
||||
} else {
|
||||
normalizeDomain(uri)
|
||||
}
|
||||
}
|
||||
|
||||
fun addUser(db: AppDatabase, account: Account, instance_uri: String, activeUser: Boolean = true, accessToken: String) {
|
||||
db.userDao().insertUser(
|
||||
UserDatabaseEntity(
|
||||
user_id = account.id,
|
||||
//make sure not to normalize to https when localhost, to allow testing
|
||||
instance_uri = normalizeOrNot(instance_uri),
|
||||
username = account.username,
|
||||
display_name = account.display_name,
|
||||
avatar_static = account.avatar_static,
|
||||
isActive = activeUser,
|
||||
accessToken = accessToken
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun storeInstance(db: AppDatabase, instance: Instance) {
|
||||
val maxTootChars = instance.max_toot_chars.toInt()
|
||||
val dbInstance = InstanceDatabaseEntity(
|
||||
//make sure not to normalize to https when localhost, to allow testing
|
||||
uri = normalizeOrNot(instance.uri),
|
||||
title = instance.title,
|
||||
max_toot_chars = maxTootChars,
|
||||
thumbnail = instance.thumbnail
|
||||
)
|
||||
db.instanceDao().insertInstance(dbInstance)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!!)
|
||||
|
@ -111,8 +111,8 @@ class HtmlUtils {
|
|||
}
|
||||
}
|
||||
|
||||
builder.removeSpan(span);
|
||||
builder.setSpan(customSpan, start, end, flags);
|
||||
builder.removeSpan(span)
|
||||
builder.setSpan(customSpan, start, end, flags)
|
||||
|
||||
// Add zero-width space after links in end of line to fix its too large hitbox.
|
||||
if (end >= builder.length || builder.subSequence(end, end + 1).toString() == "\n") {
|
||||
|
|
|
@ -89,7 +89,7 @@ class ImageUtils {
|
|||
e.printStackTrace()
|
||||
}
|
||||
intentShare.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
activity.startActivity(Intent.createChooser(intentShare, "Share Image"))
|
||||
activity.startActivity(Intent.createChooser(intentShare, context.getString(R.string.share_image)))
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.graphics.ColorMatrix
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -54,7 +55,7 @@ abstract class PostUtils {
|
|||
val resp = response.body()!!
|
||||
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares()
|
||||
holder.nshares.text = resp.getNShares(holder.context)
|
||||
holder.reblogger.isChecked = resp.reblogged
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
|
@ -83,7 +84,7 @@ abstract class PostUtils {
|
|||
val resp = response.body()!!
|
||||
|
||||
//Update shown share count
|
||||
holder.nshares.text = resp.getNShares()
|
||||
holder.nshares.text = resp.getNShares(holder.context)
|
||||
holder.reblogger.isChecked = resp.reblogged
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
|
@ -112,7 +113,7 @@ abstract class PostUtils {
|
|||
val resp = response.body()!!
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes()
|
||||
holder.nlikes.text = resp.getNLikes(holder.context)
|
||||
holder.liker.isChecked = resp.favourited
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
|
@ -141,7 +142,7 @@ abstract class PostUtils {
|
|||
val resp = response.body()!!
|
||||
|
||||
//Update shown like count and internal like toggle
|
||||
holder.nlikes.text = resp.getNLikes()
|
||||
holder.nlikes.text = resp.getNLikes(holder.context)
|
||||
holder.liker.isChecked = resp.favourited
|
||||
} else {
|
||||
Log.e("RESPONSE_CODE", response.code().toString())
|
||||
|
@ -221,7 +222,8 @@ abstract class PostUtils {
|
|||
Callback<Status> {
|
||||
override fun onFailure(call: Call<Status>, t: Throwable) {
|
||||
Log.e("COMMENT ERROR", t.toString())
|
||||
Toast.makeText(holder.context,"Comment error!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(holder.context, holder.context.getString(R.string.comment_error),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Status>, response: Response<Status>) {
|
||||
|
@ -233,7 +235,9 @@ abstract class PostUtils {
|
|||
//Add the comment to the comment section
|
||||
addComment(holder.context, holder.commentCont, resp.account.username, resp.content)
|
||||
|
||||
Toast.makeText(holder.context,"Comment: \"$textIn\" posted!", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(holder.context,
|
||||
holder.context.getString(R.string.comment_posted).format(textIn),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
Log.e("COMMENT SUCCESS", "posted: $textIn")
|
||||
} else {
|
||||
Log.e("ERROR_CODE", response.code().toString())
|
||||
|
@ -280,5 +284,14 @@ abstract class PostUtils {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun censorColorMatrix(): ColorMatrix {
|
||||
val array: FloatArray = floatArrayOf( 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 1f, 0f )
|
||||
return ColorMatrix(array)
|
||||
}
|
||||
|
||||
fun uncensorColorMatrix(): ColorMatrix {
|
||||
return ColorMatrix()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.h.pixeldroid.R
|
||||
|
||||
class ThemeUtils {
|
||||
companion object {
|
||||
/**
|
||||
* @brief Updates the application's theme depending on the given preferences and resources
|
||||
*/
|
||||
fun setThemeFromPreferences(preferences: SharedPreferences, resources : Resources) {
|
||||
val themes = resources.getStringArray(R.array.theme_values)
|
||||
val theme = preferences.getString("theme", "")
|
||||
Log.e("themePref", theme!!)
|
||||
//Set the theme
|
||||
when(theme) {
|
||||
//Light
|
||||
themes[1] -> {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
||||
}
|
||||
//Dark
|
||||
themes[2] -> {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
||||
}
|
||||
else -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.h.pixeldroid.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
|
||||
class Utils {
|
||||
companion object {
|
||||
fun hasInternet(context: Context): Boolean {
|
||||
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
return cm.activeNetwork != null
|
||||
}
|
||||
|
||||
fun normalizeDomain(domain: String): String {
|
||||
return "https://" + domain
|
||||
.replace("http://", "")
|
||||
.replace("https://", "")
|
||||
.trim(Char::isWhitespace)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:color="@color/icPressed" />
|
||||
<item android:state_focused="true" android:color="@color/icFocused" />
|
||||
<item android:color="@color/icActive" />
|
||||
</selector>
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 92 KiB |
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="24dp" android:viewportHeight="48"
|
||||
android:viewportWidth="48" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="m18.9609,24.1172q0,2.793 1.3867,4.3945 1.3867,1.582 3.8086,1.582 2.4023,0 3.7695,-1.6016 1.3867,-1.6016 1.3867,-4.375 0,-2.7344 -1.4063,-4.3359 -1.4063,-1.6211 -3.7891,-1.6211 -2.3633,0 -3.7695,1.6016 -1.3867,1.6016 -1.3867,4.3555zM29.6055,29.957q-1.1719,1.5039 -2.6953,2.2266 -1.5039,0.7031 -3.5156,0.7031 -3.3594,0 -5.4688,-2.4219 -2.0898,-2.4414 -2.0898,-6.3477 0,-3.9063 2.1094,-6.3477 2.1094,-2.4414 5.4492,-2.4414 2.0117,0 3.5352,0.7422 1.5234,0.7227 2.6758,2.207v-2.5586h2.793v14.375q2.8516,-0.4297 4.4531,-2.5977 1.6211,-2.1875 1.6211,-5.6445 0,-2.0898 -0.625,-3.9258 -0.6055,-1.8359 -1.8555,-3.3984 -2.0313,-2.5586 -4.9609,-3.9063 -2.9102,-1.3672 -6.3477,-1.3672 -2.4023,0 -4.6094,0.6445 -2.207,0.625 -4.082,1.875 -3.0664,1.9922 -4.8047,5.2344 -1.7188,3.2227 -1.7188,6.9922 0,3.1055 1.1133,5.8203 1.1328,2.7148 3.2617,4.7852 2.0508,2.0313 4.7461,3.0859 2.6953,1.0742 5.7617,1.0742 2.5195,0 4.9414,-0.8594 2.4414,-0.8398 4.4727,-2.4219l1.7578,2.168q-2.4414,1.8945 -5.332,2.8906 -2.8711,1.0156 -5.8398,1.0156 -3.6133,0 -6.8164,-1.2891 -3.2031,-1.2695 -5.7031,-3.7109 -2.5,-2.4414 -3.8086,-5.6445 -1.3086,-3.2227 -1.3086,-6.9141 0,-3.5547 1.3281,-6.7773 1.3281,-3.2227 3.7891,-5.6641 2.5195,-2.4805 5.8203,-3.7891 3.3008,-1.3281 6.9922,-1.3281 4.1406,0 7.6758,1.6992 3.5547,1.6992 5.957,4.8242 1.4648,1.9141 2.2266,4.1602 0.7813,2.2461 0.7813,4.6484 0,5.1367 -3.1055,8.1055 -3.1055,2.9688 -8.5742,3.0859z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L6,16l-2,2L4,4h16v12z"/>
|
||||
</vector>
|
|
@ -4,6 +4,6 @@
|
|||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/>
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17,15h2V7c0,-1.1 -0.9,-2 -2,-2H9v2h8v8zM7,17V1H5v4H1v2h4v10c0,1.1 0.9,2 2,2h10v4h2v-4h4v-2H7z"/>
|
||||
</vector>
|
|
@ -0,0 +1,25 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="48"
|
||||
android:viewportHeight="48">
|
||||
<path
|
||||
android:pathData="M24,4L24,4A20,20 0,0 1,44 24L44,24A20,20 0,0 1,24 44L24,44A20,20 0,0 1,4 24L4,24A20,20 0,0 1,24 4z"
|
||||
android:strokeWidth="0.82808512"
|
||||
android:fillColor="#7f7f7f"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M24.0666,16.1964m-6.6463,0a6.6463,6.6463 0,1 1,13.2926 0a6.6463,6.6463 0,1 1,-13.2926 0"
|
||||
android:strokeWidth="0.46640581"/>
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M24,27.5161L24,27.5161A5.4669,13.0074 90,0 1,37.0074 32.983L37.0074,32.983A5.4669,13.0074 90,0 1,24 38.4498L24,38.4498A5.4669,13.0074 90,0 1,10.9926 32.983L10.9926,32.983A5.4669,13.0074 90,0 1,24 27.5161zM10.9926,32.983l26.0147,0l0,5.4669l-26.0147,0z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M24,27.5161L24,27.5161A5.4669,13.0074 90,0 1,37.0074 32.983L37.0074,32.983A5.4669,13.0074 90,0 1,24 38.4498L24,38.4498A5.4669,13.0074 90,0 1,10.9926 32.983L10.9926,32.983A5.4669,13.0074 90,0 1,24 27.5161z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M10.9926,32.983l26.0147,0l0,5.4669l-26.0147,0z"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M15,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM6,10L6,7L4,7v3L1,10v2h3v3h2v-3h3v-2L6,10zM15,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
|
@ -0,0 +1,4 @@
|
|||
<vector android:height="18dp" android:viewportHeight="512"
|
||||
android:viewportWidth="512" android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M462.3,62.6C407.5,15.9 326,24.3 275.7,76.2L256,96.5l-19.7,-20.3C186.1,24.3 104.5,15.9 49.7,62.6c-62.8,53.6 -66.1,149.8 -9.9,207.9l193.5,199.8c12.5,12.9 32.8,12.9 45.3,0l193.5,-199.8c56.3,-58.1 53,-154.3 -9.8,-207.9z"/>
|
||||
</vector>
|
|
@ -0,0 +1,6 @@
|
|||
<vector android:height="21.6dp" android:viewportHeight="99"
|
||||
android:viewportWidth="110" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M54.5,94.5C58.2,90.74 79.39,69.25 92.63,55.83C104.5,43.8 104.24,26.26 93.46,15.39C82.67,4.5 65.23,4.55 54.5,15.48C43.76,4.55 26.32,4.5 15.54,15.38C4.75,26.25 4.5,43.79 16.36,55.83C29.6,69.25 50.79,90.74 54.5,94.5Z"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="9"/>
|
||||
</vector>
|
|
@ -0,0 +1,15 @@
|
|||
<vector android:height="24dp" android:viewportHeight="85"
|
||||
android:viewportWidth="111" android:width="31.341177dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M30.5,64.5L90.5,64.5L90.5,39.81"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="9"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M90.5,25.56L100,44.56L90.5,39.81L81,44.56Z"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="9"/>
|
||||
<path android:fillColor="#00000000"
|
||||
android:pathData="M80.5,18.5L20.5,18.5L20.5,44.19"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="9"/>
|
||||
<path android:fillColor="#FFFFFF"
|
||||
android:pathData="M20.5,58.44L11,39.44L20.5,44.19L30,39.44Z"
|
||||
android:strokeColor="#FFFFFF" android:strokeWidth="9"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- The background color, preferably the same as your normal theme -->
|
||||
<item android:drawable="@android:color/white"/>
|
||||
<!-- Your product logo - 144dp color version of your app icon -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/index"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
</layer-list>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
|
||||
<!-- The background color, preferably the same as your normal theme -->
|
||||
<item android:drawable="@android:color/black"/>
|
||||
<!-- Your product logo - 144dp color version of your app icon -->
|
||||
<item>
|
||||
<bitmap
|
||||
android:src="@drawable/index_night"
|
||||
android:gravity="center"/>
|
||||
</item>
|
||||
</layer-list>
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 104 KiB |
After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 117 KiB |
After Width: | Height: | Size: 117 KiB |
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"/>
|
||||
</vector>
|
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="@dimen/stroke_small"
|
||||
android:color="@color/icPressed" />
|
||||
<size
|
||||
android:width="@dimen/round_button_medium"
|
||||
android:height="@dimen/round_button_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="@dimen/stroke_small"
|
||||
android:color="@color/icFocused" />
|
||||
<size
|
||||
android:width="@dimen/round_button_medium"
|
||||
android:height="@dimen/round_button_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:width="@dimen/stroke_small"
|
||||
android:color="@color/icActive" />
|
||||
<size
|
||||
android:width="@dimen/round_button_medium"
|
||||
android:height="@dimen/round_button_medium" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:tint="@color/selector_ic"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF000000" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
|
||||
</vector>
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true" android:drawable="@drawable/ic_shutter_pressed" />
|
||||
<item android:state_focused="true" android:drawable="@drawable/ic_shutter_focused" />
|
||||
<item android:drawable="@drawable/ic_shutter_normal" />
|
||||
</selector>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="74"
|
||||
android:viewportHeight="74">
|
||||
<path android:fillColor="#FFFFFF" android:fillType="evenOdd"
|
||||
android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#58A0C4" android:fillType="evenOdd"
|
||||
android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright 2020 The Android Open Source Project
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="74"
|
||||
android:viewportHeight="74">
|
||||
<path android:fillColor="#FFFFFF" android:fillType="evenOdd"
|
||||
android:pathData="M73.1,37C73.1,17.0637 56.9373,0.9 37,0.9C17.0627,0.9 0.9,17.0637 0.9,37C0.9,56.9373 17.0627,73.1 37,73.1C56.9373,73.1 73.1,56.9373 73.1,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#CFD7DB" android:fillType="evenOdd"
|
||||
android:pathData="M67.4,37C67.4,53.7895 53.7895,67.4 37,67.4C20.2105,67.4 6.6,53.7895 6.6,37C6.6,20.2105 20.2105,6.6 37,6.6C53.7895,6.6 67.4,20.2105 67.4,37"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|