Feature/post creation (#83)
* added perm and features for cameraS, gps and external storage * added camera activity accessible from main activity * added button to redirect to camera activity * implementing callback flow to use camera * working camera * added texture view for camera display * added camera activity * implemented texture listener * camera not working, flow done, no feedback implemented * camera working * refactored code, still an activity * added private to internal function, better error function handling * deleted camera activity * added camera fragment * added camera fragment * refactored camera as fragment * necessary dependencies for fragment testing * initial camera fragment test * corrected access to activity form fragment * Added state changes and termination * added lines to test, to test coverage * Removed unsupported state STARTED state transition * Added basic tests to test code coverage * use layout for tests, to trigger permissions requirements * grant camera permission to app in camera test * replaced null handlers by proper function getter * changed layout, added takePictureButton * using expresso to get code coverage on camea * take picture flow not finished * dummy change to camera test to perform new build * added connection flow before test to reach main activity * can take a picture and put it to ImageView * replaced button text with images * smaller buttons * test camera fragment buttons * added orientation handler * changed icon to make travis happy * test new espresso config for travis * removed useless rule * deleted useless val * added layout ID's * moved swipes from Before to Tests, and thread sleep * stoped swiping, now tests from fragment directly * start post creation flow * use Uri when taking photo, can now go back from picture preview * adjusted test and flow idea * tests on displayed UI elements for the post creation fragment * refactor camera fragment into transition new post fragemnt * finished first phase: get a picture Uri * fixed lint error found by travis CI * added global timeout to test * test the new way of test * refactor new way of testing * added in-app camera view and linked everything to the final flow + started API to post * strugling on the upload media part * upload image on server implemented * post upload implemented * added API call to get max_toot_chars and correct def of a post description * fixed some tests * fix tests: clicking on tabs make the app crash because of the camera fragment * comment problematic chunk of code while samuel tries to fix it * switch minimumsdk to api 24 * Revert "switch minimumsdk to api 24" This reverts commit 24ce46dd82038b59732fd958e5e071ded39cd549. * deactivited live camera for API 23 * tests for post creation fragment UI elements * remove worthless UI testing and add gallery intent test * removed camera intent for now * some refactor * lint error and more refactor * more refactor on merge from master * refactor and test for PostCreationActivity * Revert "refactor and test for PostCreationActivity" This reverts commit a0c146bcc545cdc3792df4806e6b0c908bd18747. * Revert "Revert "refactor and test for PostCreationActivity"" This reverts commit 147a9ed80d5f9c9e3c38b5a977786bfb39eeb1b6. * permissions correction for test * updtated test * fix a test and refactor * relink correct fragment * save picture locally * test post button * requested changes * fixed required changes * Revert "fixed required changes" This reverts commit 405a9d4d1af05353e30028e60041cc1c97569c1b. * redo change request * added /media api response to mockserver Co-authored-by: Andrea Clement <samuel.dietz@epfl.ch>
This commit is contained in:
parent
3506f034d4
commit
92c534ca1b
@ -99,6 +99,13 @@ dependencies {
|
||||
implementation 'androidx.paging:paging-runtime-ktx:2.1.2'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
|
||||
|
||||
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.2.0'
|
||||
|
||||
def fragment_version = '1.2.4'
|
||||
debugImplementation "androidx.fragment:fragment-testing:$fragment_version"
|
||||
|
||||
|
||||
}
|
||||
|
||||
tasks.withType(Test) {
|
||||
|
@ -8,18 +8,14 @@ import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
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.RecyclerViewActions
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.matcher.BundleMatchers.hasValue
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||
import androidx.test.espresso.matcher.ViewMatchers
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
@ -28,8 +24,6 @@ import com.google.android.material.tabs.TabLayout
|
||||
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.CustomMatchers
|
||||
import com.h.pixeldroid.testUtility.CustomMatchers.Companion.first
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import org.hamcrest.CoreMatchers
|
||||
import org.hamcrest.Matcher
|
||||
|
@ -15,7 +15,6 @@ 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.intent.rule.IntentsTestRule
|
||||
import androidx.test.espresso.matcher.ViewMatchers.hasErrorText
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
|
@ -29,7 +29,7 @@ import org.junit.runner.RunWith
|
||||
class MockedServerTest {
|
||||
|
||||
private val mockServer = MockServer()
|
||||
|
||||
private lateinit var activityScenario: ActivityScenario<MainActivity>
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(100)
|
||||
@ -45,13 +45,15 @@ class MockedServerTest {
|
||||
.targetContext.getSharedPreferences("com.h.pixeldroid.pref", Context.MODE_PRIVATE)
|
||||
preferences.edit().putString("accessToken", "azerty").apply()
|
||||
preferences.edit().putString("domain", baseUrl.toString()).apply()
|
||||
activityScenario = ActivityScenario.launch(MainActivity::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFollowersTextView() {
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity{
|
||||
activityScenario.onActivity{
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
|
||||
}
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.nbFollowersTextView)).check(matches(withText("68\nFollowers")))
|
||||
onView(withId(R.id.accountNameTextView)).check(matches(withText("deerbard_photo")))
|
||||
@ -136,29 +138,31 @@ class MockedServerTest {
|
||||
|
||||
@Test
|
||||
fun testNotificationsList() {
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity{
|
||||
activityScenario.onActivity{
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(3)?.select()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
|
||||
onView(withText("Dobios liked your post")).check(matches(withId(R.id.notification_type)))
|
||||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeDown())
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withText("Dobios followed you")).check(matches(withId(R.id.notification_type)))
|
||||
|
||||
}
|
||||
@Test
|
||||
fun clickNotification() {
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity{
|
||||
activityScenario.onActivity{
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(3)?.select()
|
||||
}
|
||||
Thread.sleep(1000)
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.view_pager)).perform(ViewActions.swipeUp()).perform(ViewActions.swipeDown())
|
||||
Thread.sleep(1000)
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withText("Dobios liked your post")).perform(ViewActions.click())
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withText("6 Likes")).check(matches(withId(R.id.nlikes)))
|
||||
}
|
||||
@ -209,9 +213,11 @@ class MockedServerTest {
|
||||
|
||||
@Test
|
||||
fun swipingRightStopsAtHomepage() {
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
activityScenario.onActivity {
|
||||
a -> a.findViewById<TabLayout>(R.id.tabs).getTabAt(4)?.select()
|
||||
} // go to the last tab
|
||||
|
||||
Thread.sleep(1000)
|
||||
onView(withId(R.id.main_activity_main_linear_layout))
|
||||
.perform(ViewActions.swipeRight()) // notifications
|
||||
.perform(ViewActions.swipeRight()) // camera
|
||||
@ -445,5 +451,10 @@ class MockedServerTest {
|
||||
onView(first(withId(R.id.commentContainer)))
|
||||
.check(matches(hasDescendant(withId(R.id.comment))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun instanceConfigurationTest() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,51 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
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 PostCreationActivityTest {
|
||||
|
||||
val mockServer = MockServer()
|
||||
|
||||
@get:Rule
|
||||
val globalTimeout: Timeout = Timeout.seconds(30)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
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()
|
||||
|
||||
val uri: Uri = Uri.parse("android.resource://com.h.pixeldroid/drawable/index")
|
||||
val intent = Intent(context, PostCreationActivity::class.java)
|
||||
.putExtra("picture_uri", uri)
|
||||
|
||||
ActivityScenario.launch<PostCreationActivity>(intent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createPost() {
|
||||
onView(withId(R.id.post_creation_send_button)).perform(click())
|
||||
// should send on main activity
|
||||
onView(withId(R.id.main_activity_main_linear_layout)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.h.pixeldroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.action.ViewActions.*
|
||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||
import androidx.test.espresso.intent.Intents.intended
|
||||
import androidx.test.espresso.intent.Intents.intending
|
||||
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
|
||||
import androidx.test.espresso.intent.rule.IntentsTestRule
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.h.pixeldroid.testUtility.MockServer
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import org.hamcrest.Matcher
|
||||
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 PostCreationFragmentTest {
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(30)
|
||||
@get:Rule
|
||||
var runtimePermissionRule: GrantPermissionRule =
|
||||
GrantPermissionRule.grant(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
@get:Rule
|
||||
var intentsTestRule: IntentsTestRule<MainActivity> =
|
||||
IntentsTestRule(MainActivity::class.java)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
onView(withId(R.id.main_activity_main_linear_layout))
|
||||
.perform(swipeLeft())
|
||||
.perform(swipeLeft())
|
||||
Thread.sleep(300)
|
||||
}
|
||||
|
||||
// upload intent
|
||||
@Test
|
||||
fun uploadButtonLaunchesGalleryIntent() {
|
||||
val expectedIntent: Matcher<Intent> = hasAction(Intent.ACTION_CHOOSER)
|
||||
intending(expectedIntent)
|
||||
onView(withId(R.id.uploadPictureButton)).perform(click())
|
||||
Thread.sleep(1000)
|
||||
intended(expectedIntent)
|
||||
}
|
||||
}
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PostFragmentUITests {
|
||||
private val mockServer = MockServer()
|
||||
|
||||
@get:Rule
|
||||
var globalTimeout: Timeout = Timeout.seconds(30)
|
||||
@get:Rule
|
||||
var rule = ActivityScenarioRule(MainActivity::class.java)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
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()
|
||||
|
||||
ActivityScenario.launch(MainActivity::class.java).onActivity {
|
||||
a -> a.tabs.getTabAt(2)!!.select()
|
||||
}
|
||||
Thread.sleep(300)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun newPostUiTest() {
|
||||
onView(withId(R.id.uploadPictureButton)).check(matches(isDisplayed()))
|
||||
onView(withId(R.id.takePictureButton)).check(matches(isDisplayed()))
|
||||
}
|
||||
}
|
@ -127,6 +127,36 @@ class MockServer {
|
||||
|
||||
val reblogJson = """{"id":"156491373246287872","created_at":"2020-04-16T20:00:50.000000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"https:\/\/pixelfed.de\/p\/machintuck\/156491373246287872","url":"https:\/\/pixelfed.de\/p\/machintuck\/156491373246287872","replies_count":1,"reblogs_count":14,"favourites_count":2,"reblogged":true,"favourited":false,"muted":false,"bookmarked":false,"pinned":false,"content":"<a class=\"u-url mention\" href=\"https:\/\/pixelfed.de\/Dobios\" rel=\"external nofollow noopener\">@Dobios<\/a> <a class=\"u-url mention\" href=\"https:\/\/pixelfed.de\/Dante\" rel=\"external nofollow noopener\">@Dante<\/a>","reblog":null,"application":{"name":"web","website":null},"mentions":[{"id":"136800034732773376","url":"https:\/\/pixelfed.de\/Dobios","username":"Dobios","acct":"Dobios"},{"id":"136453537340198912","url":"https:\/\/pixelfed.de\/dante","username":"dante","acct":"dante"}],"tags":[{"name":"mushroom","url":"https:\/\/pixelfed.de\/discover\/tags\/mushroom"},{"name":"commentsstillbroken","url":"https:\/\/pixelfed.de\/discover\/tags\/commentsstillbroken"},{"name":"fixyourapi","url":"https:\/\/pixelfed.de\/discover\/tags\/fixyourapi"},{"name":"pls","url":"https:\/\/pixelfed.de\/discover\/tags\/pls"}],"emojis":[],"card":null,"poll":null,"account":{"id":"145183325781364736","username":"machintuck","acct":"machintuck","display_name":"Arthur","locked":false,"created_at":"2020-03-16T15:06:42.000000Z","followers_count":4,"following_count":4,"statuses_count":5,"note":"","url":"https:\/\/pixelfed.de\/machintuck","avatar":"https:\/\/pixelfed.de\/storage\/avatars\/014\/518\/332\/578\/136\/473\/6\/gbdKtKOhTkNA5UxCzeAQ_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","avatar_static":"https:\/\/pixelfed.de\/storage\/avatars\/014\/518\/332\/578\/136\/473\/6\/gbdKtKOhTkNA5UxCzeAQ_avatar.jpeg?v=d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35","header":"","header_static":"","emojis":[],"moved":null,"fields":null,"bot":false,"software":"pixelfed","is_admin":false},"media_attachments":[{"id":"19228","type":"image","url":"https:\/\/pixelfed.de\/storage\/m\/d0931bf747b992a1c83e055753526516f2706111\/9b4393bfd32c643a265bd1c557b981f167d60969\/lbOqQOMeHLGmhYgehhZUBJ4JvjtKulh83BA97LoP.jpeg","remote_url":null,"preview_url":"https:\/\/pixelfed.de\/storage\/m\/d0931bf747b992a1c83e055753526516f2706111\/9b4393bfd32c643a265bd1c557b981f167d60969\/lbOqQOMeHLGmhYgehhZUBJ4JvjtKulh83BA97LoP_thumb.jpeg","text_url":null,"meta":null,"description":null}]}"""
|
||||
|
||||
val mediaUploadResponseJson = """
|
||||
{
|
||||
"id": "22348641",
|
||||
"type": "image",
|
||||
"url": "https://files.mastodon.social/media_attachments/files/022/348/641/original/cebc6d51be03e509.jpeg",
|
||||
"preview_url": "https://files.mastodon.social/media_attachments/files/022/348/641/small/cebc6d51be03e509.jpeg",
|
||||
"remote_url": null,
|
||||
"text_url": "https://mastodon.social/media/4Zj6ewxzzzDi0g8JnZQ",
|
||||
"meta": {
|
||||
"focus": {
|
||||
"x": -0.69,
|
||||
"y": 0.42
|
||||
},
|
||||
"original": {
|
||||
"width": 640,
|
||||
"height": 480,
|
||||
"size": "640x480",
|
||||
"aspect": 1.3333333333333333
|
||||
},
|
||||
"small": {
|
||||
"width": 461,
|
||||
"height": 346,
|
||||
"size": "461x346",
|
||||
"aspect": 1.3323699421965318
|
||||
}
|
||||
},
|
||||
"description": "test uploaded via api",
|
||||
"blurhash": "UFBWY:8_0Jxv4mx]t8t64.%M-:IUWGWAt6M}"
|
||||
}
|
||||
"""
|
||||
val followRelationshipJson = """{"id":"136800034732773376","following":true,"followed_by":true,"blocking":false,"muting":false,"muting_notifications":null,"requested":false,"domain_blocking":null,"showing_reblogs":null,"endorsed":false}"""
|
||||
val unfollowRelationshipJson = """{"id":"136800034732773376","following":false,"followed_by":true,"blocking":false,"muting":false,"muting_notifications":null,"requested":false,"domain_blocking":null,"showing_reblogs":null,"endorsed":false}"""
|
||||
val relationshipJson = """[{"id":"136800034732773376","following":true,"followed_by":true,"blocking":false,"muting":false,"muting_notifications":null,"requested":false,"domain_blocking":null,"showing_reblogs":null,"endorsed":false}]"""
|
||||
@ -145,6 +175,7 @@ class MockServer {
|
||||
when (request.path) {
|
||||
"/api/v1/accounts/verify_credentials" -> return MockResponse().addHeader("Content-Type", "application/json; charset=utf-8").setResponseCode(200).setBody(accountJson)
|
||||
"/api/v1/timelines/home" -> return MockResponse().addHeader("Content-Type", "application/json; charset=utf-8").setResponseCode(200).setBody(feedJson)
|
||||
"/api/v1/media" -> return MockResponse().addHeader("Content-Type", "application/json; charset=utf-8").setResponseCode(200).setBody(mediaUploadResponseJson)
|
||||
}
|
||||
when {
|
||||
request.path?.startsWith("/api/v1/notifications") == true -> {
|
||||
|
@ -3,7 +3,13 @@
|
||||
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.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.any"
|
||||
android:required="false" />
|
||||
<uses-feature android:name="android.hardware.location.gps" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -13,6 +19,7 @@
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".PostCreationActivity" />
|
||||
<activity android:name=".FollowsActivity" />
|
||||
<activity android:name=".PostActivity" />
|
||||
<activity android:name=".ProfileActivity" />
|
||||
@ -47,6 +54,16 @@
|
||||
android:scheme="@string/auth_scheme" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="com.h.pixeldroid.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -6,12 +6,14 @@ 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.objects.Application
|
||||
import com.h.pixeldroid.objects.Instance
|
||||
import com.h.pixeldroid.objects.Token
|
||||
import kotlinx.android.synthetic.main.activity_login.*
|
||||
import okhttp3.HttpUrl
|
||||
@ -22,6 +24,8 @@ import retrofit2.Response
|
||||
|
||||
class LoginActivity : AppCompatActivity() {
|
||||
|
||||
private val TAG = "Login Activity"
|
||||
|
||||
private lateinit var OAUTH_SCHEME: String
|
||||
private val PACKAGE_ID = BuildConfig.APPLICATION_ID
|
||||
private val SCOPE = "read write follow"
|
||||
@ -81,6 +85,7 @@ class LoginActivity : AppCompatActivity() {
|
||||
preferences.edit()
|
||||
.putString("domain", "https://$normalizedDomain")
|
||||
.apply()
|
||||
getInstanceConfig()
|
||||
registerAppToServer("https://$normalizedDomain")
|
||||
|
||||
}
|
||||
@ -174,7 +179,7 @@ class LoginActivity : AppCompatActivity() {
|
||||
if (!response.isSuccessful || response.body() == null) {
|
||||
return failedRegistration(getString(R.string.token_error))
|
||||
}
|
||||
authenticationSuccessful(domain, response.body()!!.access_token)
|
||||
authenticationSuccessful(response.body()!!.access_token)
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<Token>, t: Throwable) {
|
||||
@ -189,7 +194,7 @@ class LoginActivity : AppCompatActivity() {
|
||||
).enqueue(callback)
|
||||
}
|
||||
|
||||
private fun authenticationSuccessful(domain: String, accessToken: String) {
|
||||
private fun authenticationSuccessful(accessToken: String) {
|
||||
preferences.edit().putString("accessToken", accessToken).apply()
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
startActivity(intent)
|
||||
@ -213,4 +218,30 @@ class LoginActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.h.pixeldroid.fragments.CameraFragment
|
||||
import com.h.pixeldroid.fragments.NewPostFragment
|
||||
import com.h.pixeldroid.fragments.feeds.HomeFragment
|
||||
import com.h.pixeldroid.fragments.ProfileFragment
|
||||
import com.h.pixeldroid.fragments.feeds.NotificationsFragment
|
||||
@ -49,7 +49,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte
|
||||
val tabs = arrayOf(
|
||||
HomeFragment(),
|
||||
Fragment(),
|
||||
CameraFragment(),
|
||||
NewPostFragment(),
|
||||
NotificationsFragment(),
|
||||
ProfileFragment()
|
||||
)
|
||||
|
162
app/src/main/java/com/h/pixeldroid/PostCreationActivity.kt
Normal file
162
app/src/main/java/com/h/pixeldroid/PostCreationActivity.kt
Normal file
@ -0,0 +1,162 @@
|
||||
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
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
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.objects.Attachment
|
||||
import com.h.pixeldroid.objects.Status
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
class PostCreationActivity : AppCompatActivity() {
|
||||
|
||||
private val TAG = "Post Creation Activity"
|
||||
|
||||
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 description: String = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_post_creation)
|
||||
|
||||
val imageUri: Uri = intent.getParcelableExtra<Uri>("picture_uri")!!
|
||||
|
||||
saveImage(imageUri)
|
||||
|
||||
pictureFrame = findViewById<ImageView>(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", "")!!
|
||||
|
||||
// check if the picture is alright
|
||||
// TODO
|
||||
|
||||
// edit the picture
|
||||
// TODO
|
||||
|
||||
// get the description and send the post to PixelFed
|
||||
findViewById<Button>(R.id.post_creation_send_button).setOnClickListener {
|
||||
if (setDescription()) upload()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveImage(imageUri: Uri) {
|
||||
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
|
||||
val fileName = "PixelDroid_$timeStamp.png"
|
||||
try {
|
||||
val stream = applicationContext.contentResolver
|
||||
.openAssetFileDescriptor(imageUri, "r")!!
|
||||
.createInputStream()
|
||||
val bm = BitmapFactory.decodeStream(stream)
|
||||
val bos = ByteArrayOutputStream()
|
||||
bm.compress(Bitmap.CompressFormat.PNG, 0, bos)
|
||||
image = File(
|
||||
applicationContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES),
|
||||
fileName)
|
||||
val fos = FileOutputStream(image)
|
||||
fos.write(bos.toByteArray())
|
||||
fos.flush()
|
||||
fos.close()
|
||||
} catch (error: IOException) {
|
||||
error.printStackTrace()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
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."
|
||||
return false
|
||||
}
|
||||
// store the description
|
||||
description = content
|
||||
return true
|
||||
}
|
||||
|
||||
private fun upload() {
|
||||
val rBody: RequestBody = image.asRequestBody("image/*".toMediaTypeOrNull())
|
||||
val part = MultipartBody.Part.createFormData("file", image.name, rBody)
|
||||
pixelfedAPI.mediaUpload("Bearer $accessToken", part).enqueue(object:
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call<Attachment>, response: Response<Attachment>) {
|
||||
if (response.code() == 200) {
|
||||
val body = response.body()!!
|
||||
if (body.type.name == "image") {
|
||||
post(body.id)
|
||||
} else
|
||||
Toast.makeText(applicationContext, "Upload error: wrong picture format.", 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()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun post(id: String) {
|
||||
if (id.isEmpty()) return
|
||||
pixelfedAPI.postStatus(
|
||||
authorization = "Bearer $accessToken",
|
||||
statusText = description,
|
||||
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()
|
||||
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()
|
||||
startActivity(Intent(applicationContext, MainActivity::class.java))
|
||||
} else {
|
||||
Toast.makeText(applicationContext,"Post upload failed : not 200",Toast.LENGTH_SHORT).show()
|
||||
Log.e(TAG, call.request().toString() + response.raw().toString())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package com.h.pixeldroid.api
|
||||
|
||||
import com.h.pixeldroid.objects.*
|
||||
import okhttp3.MultipartBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
@ -8,7 +9,6 @@ import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.http.*
|
||||
import retrofit2.http.Field
|
||||
|
||||
|
||||
/*
|
||||
Implements the Pixelfed API
|
||||
https://docs.pixelfed.org/technical-documentation/api-v1.html
|
||||
@ -74,14 +74,14 @@ interface PixelfedAPI {
|
||||
@Path("id") statusId: String
|
||||
) : Call<List<Account>>
|
||||
|
||||
//Used in our case to post a comment
|
||||
//Used in our case to post a comment or a status
|
||||
@FormUrlEncoded
|
||||
@POST("/api/v1/statuses")
|
||||
fun postStatus(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String,
|
||||
@Field("status") statusText : String,
|
||||
@Field("in_reply_to_id") in_reply_to_id : String,
|
||||
@Field("in_reply_to_id") in_reply_to_id : String? = null,
|
||||
@Field("media_ids[]") media_ids : List<String> = emptyList(),
|
||||
@Field("poll[options][]") poll_options : List<String>? = null,
|
||||
@Field("poll[expires_in]") poll_expires : List<String>? = null,
|
||||
@ -203,5 +203,17 @@ interface PixelfedAPI {
|
||||
.build().create(PixelfedAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Multipart
|
||||
@POST("/api/v1/media")
|
||||
fun mediaUpload(
|
||||
//The authorization header needs to be of the form "Bearer <token>"
|
||||
@Header("Authorization") authorization: String,
|
||||
@Part file: MultipartBody.Part
|
||||
): Call<Attachment>
|
||||
|
||||
// get instance configuration
|
||||
@GET("/api/v1/instance")
|
||||
fun instance() : Call<Instance>
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,8 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
fun getDatabase(context: Context): AppDatabase {
|
||||
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
var instance: AppDatabase? = null
|
||||
|
||||
// To be able to create a temporary database that flushes when tests are over
|
||||
instance = if (TEST_MODE) {
|
||||
var instance = if (TEST_MODE) {
|
||||
Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).allowMainThreadQueries().build()
|
||||
} else {
|
||||
Room.databaseBuilder(
|
||||
|
@ -3,54 +3,57 @@ package com.h.pixeldroid.fragments
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.h.pixeldroid.PostCreationActivity
|
||||
import com.h.pixeldroid.R
|
||||
|
||||
class CameraFragment : Fragment() {
|
||||
/**
|
||||
* 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
|
||||
private val TAG = "Camera Fragment"
|
||||
|
||||
private var uploadedPictureView: ImageView? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_camera, container, false)
|
||||
val uploadPictureButton: Button = view.findViewById(R.id.upload_picture_button)
|
||||
uploadedPictureView = view.findViewById(R.id.uploaded_picture_view)
|
||||
val view = inflater.inflate(R.layout.fragment_new_post, container, false)
|
||||
|
||||
val uploadPictureButton: Button = view.findViewById(R.id.uploadPictureButton)
|
||||
uploadPictureButton.setOnClickListener{
|
||||
uploadPicture()
|
||||
}
|
||||
|
||||
// Inflate the layout for this fragment
|
||||
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, PostCreationActivity::class.java)
|
||||
.putExtra("picture_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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK) {
|
||||
if(data == null || data.data == null){
|
||||
Log.w(TAG, "No picture uploaded")
|
||||
return
|
||||
}
|
||||
uploadedPictureView?.setImageURI(data.data)
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ 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.POST_TAG
|
||||
import kotlinx.android.synthetic.main.post_fragment.view.*
|
||||
|
||||
|
||||
class PostFragment : Fragment() {
|
||||
|
12
app/src/main/java/com/h/pixeldroid/objects/Instance.kt
Normal file
12
app/src/main/java/com/h/pixeldroid/objects/Instance.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.h.pixeldroid.objects
|
||||
|
||||
data class Instance (
|
||||
val description: String,
|
||||
val email: String,
|
||||
val max_toot_chars: String = "500",
|
||||
val registrations: Boolean,
|
||||
val thumbnail: String,
|
||||
val title: String,
|
||||
val uri: String,
|
||||
val version: String
|
||||
)
|
@ -107,12 +107,12 @@ data class Status(
|
||||
}
|
||||
|
||||
fun getNLikes() : CharSequence {
|
||||
val nLikes : Int = favourites_count ?: 0
|
||||
val nLikes = favourites_count
|
||||
return "$nLikes Likes"
|
||||
}
|
||||
|
||||
fun getNShares() : CharSequence {
|
||||
val nShares : Int = reblogs_count ?: 0
|
||||
val nShares = reblogs_count
|
||||
return "$nShares Shares"
|
||||
}
|
||||
|
||||
@ -257,7 +257,7 @@ data class Status(
|
||||
if (replies_count == 0) {
|
||||
holder.viewComment.text = "No comments on this post..."
|
||||
} else {
|
||||
holder.viewComment.text = "View all ${replies_count} comments..."
|
||||
holder.viewComment.text = "View all $replies_count comments..."
|
||||
holder.viewComment.setOnClickListener {
|
||||
holder.viewComment.visibility = View.GONE
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="356.388dp"
|
||||
android:height="280dp"
|
||||
android:viewportWidth="356.388"
|
||||
|
57
app/src/main/res/layout/activity_camera.xml
Normal file
57
app/src/main/res/layout/activity_camera.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/camera_fragment_main_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".fragments.NewPostFragment">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/textureView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/takePictureButton"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="30dp"
|
||||
android:layout_margin="15dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:background="?android:attr/listChoiceIndicatorSingle"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:textColor="@color/cardview_light_background"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.517"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="1.0"
|
||||
tools:ignore="MissingConstraints,PrivateResource" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/uploadedPictureView"
|
||||
android:layout_width="366dp"
|
||||
android:layout_height="532dp"
|
||||
android:layout_margin="15dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginEnd="14dp"
|
||||
android:contentDescription="@string/upload_a_picture"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/textureView"
|
||||
app:layout_constraintHorizontal_bias="0.501"
|
||||
app:layout_constraintStart_toEndOf="@+id/textureView"
|
||||
app:layout_constraintTop_toTopOf="@+id/textureView"
|
||||
app:layout_constraintVertical_bias="0.147"
|
||||
tools:ignore="MissingConstraints"
|
||||
tools:srcCompat="@tools:sample/avatars" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
55
app/src/main/res/layout/activity_post_creation.xml
Normal file
55
app/src/main/res/layout/activity_post_creation.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/post_creation_picture_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="#88000000">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/new_post_description_input_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/description"
|
||||
app:errorEnabled="true"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:paddingStart="15dp"
|
||||
android:textColorHint="@color/cardview_light_background"
|
||||
app:errorTextColor="@color/cardview_light_background">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/new_post_description_input_field"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ems="10"
|
||||
android:inputType="textMultiLine"
|
||||
android:textColor="@color/cardview_light_background"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/post_creation_send_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/send"
|
||||
android:background="@color/colorPrimary"
|
||||
android:textColor="@color/cardview_light_background"
|
||||
android:layout_margin="15dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:ignore="PrivateResource"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/camera_fragment_main_linear_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
tools:context=".fragments.CameraFragment"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@+id/upload_picture_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:layout_margin="15dp"
|
||||
android:text="@string/upload_a_picture"
|
||||
android:background="@color/colorPrimary"
|
||||
android:textColor="@color/cardview_light_background"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/uploaded_picture_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
tools:srcCompat="@tools:sample/avatars" />
|
||||
|
||||
</LinearLayout>
|
48
app/src/main/res/layout/fragment_new_post.xml
Normal file
48
app/src/main/res/layout/fragment_new_post.xml
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/create_a_new_post"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="25sp"
|
||||
android:layout_marginBottom="50dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/uploadPictureButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:text="@string/upload_a_picture"
|
||||
android:background="@color/colorPrimary"
|
||||
android:textColor="@color/cardview_light_background"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/or"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/takePictureButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="15dp"
|
||||
android:gravity="center"
|
||||
android:padding="15dp"
|
||||
android:text="@string/take_a_picture"
|
||||
android:background="@color/colorPrimary"
|
||||
android:textColor="@color/cardview_light_background"
|
||||
tools:ignore="PrivateResource" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -54,15 +54,23 @@
|
||||
</string>
|
||||
<string name="attachment_summary_off">Only download attachments when manually requested</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="start_login">Start Login</string>
|
||||
<string name="no_username">No Username</string>
|
||||
<string name="upload_a_picture">Upload a picture</string>
|
||||
<string name="followed_notification">%1$s followed you</string>
|
||||
<string name="mention_notification">%1$s mentioned you</string>
|
||||
<string name="shared_notification">%1$s shared your post</string>
|
||||
<string name="liked_notification">%1$s liked your post</string>
|
||||
|
||||
<string name="create_a_new_post">Create a new post!</string>
|
||||
<string name="take_a_picture">Take a picture</string>
|
||||
<string name="upload_a_picture">Upload a picture</string>
|
||||
<string name="or">or</string>
|
||||
|
||||
<string name="description">Description…</string>
|
||||
<string name="upload">upload</string>
|
||||
<string name="send">send</string>
|
||||
<string name="reconnect">Reconnect</string>
|
||||
|
||||
<string name="whats_an_instance">What\'s an instance?</string>
|
||||
<string name="logout">Logout</string>
|
||||
|
||||
|
4
app/src/main/res/xml/file_paths.xml
Normal file
4
app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-files-path name="my_images" path="Android/data/com.h.pixeldroid/files/Pictures" />
|
||||
</paths>
|
@ -1,13 +1,13 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.61'
|
||||
ext.kotlin_version = '1.3.72'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.6.2'
|
||||
classpath 'com.android.tools.build:gradle:3.6.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
Loading…
x
Reference in New Issue
Block a user