Compare commits

..

8 Commits

Author SHA1 Message Date
Mateusz Hinc
962c5df78e fix layout 2020-01-24 19:07:03 +01:00
Mateusz Hinc
bd164ff832 snapchat 2020-01-12 11:44:58 +01:00
Mateusz Hinc
61f2af2cb9 instagram 2020-01-11 18:18:34 +01:00
Mateusz Hinc
066400f700 facebook 2020-01-09 20:59:24 +01:00
Mateusz Hinc
4c00eaaa1b facebook 2020-01-06 19:49:59 +01:00
Mateusz Hinc
0c8ec7fd28 #SA-16 #SA-29 #SA-4 improve design, add post history activity 2019-12-15 14:59:34 +01:00
Mateusz Hinc
9fc3c124bf add automated android tests 2019-12-12 21:23:11 +01:00
8130012710 Merge branch 'feature/SA-28-facebook-api-poc-impl' of s452086/social-aggregator into master 2019-12-09 16:12:41 +00:00
83 changed files with 3040 additions and 228 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="" vcs="Git" />
</component>
</project>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,16 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
@ -39,9 +49,36 @@ dependencies {
implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.2.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
androidTestImplementation 'androidx.test:core-ktx:1.2.0'
androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
androidTestImplementation 'androidx.test:runner:' + rootProject.runnerVersion
androidTestImplementation 'androidx.test:rules:' + rootProject.rulesVersion
androidTestImplementation 'androidx.test.espresso:espresso-core:' + rootProject.espressoVersion
androidTestImplementation 'androidx.test.espresso:espresso-intents:' + rootProject.espressoVersion
androidTestImplementation 'androidx.test.espresso:espresso-contrib:' + rootProject.espressoVersion
androidTestImplementation 'androidx.test.ext:truth:' + rootProject.extTruthVersion
androidTestImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
androidTestImplementation 'org.robolectric:annotations:' + rootProject.robolectricVersion
testImplementation 'androidx.test:core:' + rootProject.coreVersion
testImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
testImplementation 'androidx.test.espresso:espresso-core:' + rootProject.espressoVersion
testImplementation 'androidx.test.espresso:espresso-intents:' + rootProject.espressoVersion
testImplementation 'androidx.test.espresso:espresso-contrib:' + rootProject.espressoVersion
testImplementation 'androidx.test.ext:truth:' + rootProject.extTruthVersion
testImplementation 'androidx.test.ext:junit:' + rootProject.extJUnitVersion
testImplementation 'org.robolectric:robolectric:' + rootProject.robolectricVersion
implementation 'com.facebook.android:facebook-login:[5,6)'
implementation 'com.facebook.android:facebook-share:[5,6)'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation([
'com.snapchat.kit.sdk:creative:1.1.4',
'com.snapchat.kit.sdk:core:1.1.4'
])
}

View File

@ -0,0 +1,82 @@
package pl.edu.amu.wmi.socialaggregator
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.launchActivity
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import pl.edu.amu.wmi.socialaggregator.TestUtils.actionOnChild
import pl.edu.amu.wmi.socialaggregator.TestUtils.cleanLocalStorage
import pl.edu.amu.wmi.socialaggregator.activity.AddSocialActivity
@RunWith(AndroidJUnit4::class)
class AddSocialActivityTest {
@Rule
@JvmField
val activity = IntentsTestRule(AddSocialActivity::class.java)
@Test
fun initialState_noAddedSocials_oneAvailableSocial() {
cleanLocalStorage(activity)
val newActivity = launchActivity<AddSocialActivity>()
assertEquals(
0, activity.activity.findViewById<RecyclerView>(R.id.availableSocialsRecyclerView)
.adapter?.itemCount
)
assertEquals(
1, activity.activity.findViewById<RecyclerView>(R.id.addSocialRecyclerView)
.adapter?.itemCount
)
}
@Test
fun clickingSocials_movesThemFormAvailableToAdded_etViceVersa() {
cleanLocalStorage(activity)
onView(withId(R.id.addSocialRecyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
actionOnChild(click(), R.id.socialPlatformImage)
)
)
assertEquals(
1, activity.activity.findViewById<RecyclerView>(R.id.availableSocialsRecyclerView)
.adapter?.itemCount
)
assertEquals(
0, activity.activity.findViewById<RecyclerView>(R.id.addSocialRecyclerView)
.adapter?.itemCount
)
onView(withId(R.id.availableSocialsRecyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
actionOnChild(click(), R.id.socialPlatformImage)
)
)
assertEquals(
0, activity.activity.findViewById<RecyclerView>(R.id.availableSocialsRecyclerView)
.adapter?.itemCount
)
assertEquals(
1, activity.activity.findViewById<RecyclerView>(R.id.addSocialRecyclerView)
.adapter?.itemCount
)
}
}

View File

@ -15,10 +15,12 @@ import org.junit.Assert.*
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
appContext.filesDir.delete()
assertEquals("pl.edu.amu.wmi.socialaggregator", appContext.packageName)
}
}

View File

@ -0,0 +1,38 @@
package pl.edu.amu.wmi.socialaggregator
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import pl.edu.amu.wmi.socialaggregator.activity.AddSocialActivity
import pl.edu.amu.wmi.socialaggregator.activity.MainActivity
import pl.edu.amu.wmi.socialaggregator.activity.NewPostActivity
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@Rule
@JvmField
val activity = IntentsTestRule(MainActivity::class.java)
@Test
fun clickAddNewSocial_shouldOpenAddNewSocialActivity() {
onView(withId(R.id.connectedSocialsButton)).perform(click())
intended(hasComponent(AddSocialActivity::class.java.name))
}
@Test
fun clickCreatePost_shouldOpenAddNewNewPostActivity() {
onView(withId(R.id.createPostButton)).perform(click())
intended(hasComponent(NewPostActivity::class.java.name))
}
}

View File

@ -0,0 +1,73 @@
package pl.edu.amu.wmi.socialaggregator
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.*
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import pl.edu.amu.wmi.socialaggregator.TestUtils.atPosition
import pl.edu.amu.wmi.socialaggregator.TestUtils.cleanLocalStorage
import pl.edu.amu.wmi.socialaggregator.TestUtils.getCurrentActivity
import pl.edu.amu.wmi.socialaggregator.activity.MainActivity
@RunWith(AndroidJUnit4::class)
class NewPostActivityTest {
@Rule
@JvmField
val activity = IntentsTestRule(MainActivity::class.java)
@Test
fun clickPublishPost_shouldEndActivity() {
onView(withId(R.id.createPostButton)).perform(click())
closeSoftKeyboard()
onView(withId(R.id.publishPost)).perform(click())
assertTrue(getCurrentActivity()!!::class.java.simpleName == "MainActivity")
}
@Test
fun clickPublishPost_shouldIncreasePostCount() {
cleanLocalStorage(activity)
onView(withId(R.id.connectedSocialsButton)).perform(click())
onView(withId(R.id.addSocialRecyclerView))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
TestUtils.actionOnChild(click(), R.id.socialPlatformImage)
)
)
pressBack()
onView(withId(R.id.createPostButton)).perform(click())
closeSoftKeyboard()
onView(withId(R.id.availableSocials))
.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
TestUtils.actionOnChild(click(), R.id.chip)
)
)
onView(withId(R.id.publishPost)).perform(click())
onView(withId(R.id.previousPostsRecyclerView)).check(
matches(atPosition(0, hasDescendant(withText("Facebook"))))
)
}
}

View File

@ -0,0 +1,109 @@
package pl.edu.amu.wmi.socialaggregator
import android.app.Activity
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.NoMatchingViewException
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.ViewAssertion
import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.espresso.matcher.BoundedMatcher
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
import androidx.test.runner.lifecycle.Stage
import org.hamcrest.CoreMatchers
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.core.AllOf
import org.junit.runner.RunWith
import java.io.File
object TestUtils {
fun cleanLocalStorage(activity: IntentsTestRule<out Activity>) {
deleteRecursive(activity.activity.filesDir)
}
private fun deleteRecursive(fileOrDirectory: File) {
if (fileOrDirectory.isDirectory())
for (child in fileOrDirectory.listFiles())
deleteRecursive(child)
fileOrDirectory.delete()
}
fun actionOnChild(action: ViewAction, childId: Int): ViewAction? {
return object : ViewAction {
override fun getDescription(): String {
return "Action on child"
}
override fun getConstraints(): Matcher<View> {
return AllOf.allOf(
ViewMatchers.isDisplayed(),
ViewMatchers.isAssignableFrom(View::class.java)
)
}
override fun perform(uiController: UiController?, view: View?) {
view?.let {
val child = it.findViewById<View>(childId)
action.perform(uiController, child)
}
}
}
}
fun atPosition(position: Int, itemMatcher: Matcher<View>): Matcher<View> {
checkNotNull(itemMatcher)
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has item at position $position: ")
itemMatcher.describeTo(description)
}
override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position)
?: // has no item on such position
return false
return itemMatcher.matches(viewHolder.itemView)
}
}
}
fun atPosition(position: Int, id: Int, itemMatcher: Matcher<View>): Matcher<View> {
checkNotNull(itemMatcher)
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description) {
description.appendText("has item at position $position: ")
itemMatcher.describeTo(description)
}
override fun matchesSafely(view: RecyclerView): Boolean {
val viewHolder = view.findViewHolderForAdapterPosition(position)
?: // has no item on such position
return false
val view = viewHolder.itemView.findViewById<View>(id)
return itemMatcher.matches(view)
}
}
}
fun getCurrentActivity(): Activity? {
var currentActivity: Activity? = null
getInstrumentation().runOnMainSync {
run {
currentActivity =
ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED
).elementAtOrNull(0)
}
}
return currentActivity
}
}

View File

@ -2,13 +2,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.edu.amu.wmi.socialaggregator">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.PostHistoryActivity"
android:label="@string/title_activity_post_history"
android:parentActivityName=".activity.MainActivity"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="pl.edu.amu.wmi.socialaggregator.activity.MainActivity" />
</activity>
<activity
android:name=".activity.AddSocialActivity"
android:label="@string/title_activity_add_social"
@ -34,6 +46,50 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id"/>
<activity android:name="com.facebook.FacebookActivity"
android:configChanges=
"keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />
<activity
android:name="com.facebook.CustomTabActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/fb_login_protocol_scheme" />
</intent-filter>
</activity>
<provider android:authorities="com.facebook.app.FacebookContentProvider2437098899888167"
android:name="com.facebook.FacebookContentProvider"
android:exported="true"/>
<meta-data android:name="com.snapchat.kit.sdk.clientId" android:value="240836fb-b762-4be8-a42f-5472a22f2114" />
<!-- <meta-data android:name="com.snapchat.kit.sdk.clientId" android:value="your apps client id" />-->
<!-- <meta-data android:name="com.snapchat.kit.sdk.redirectUrl" android:value="the url that will handle login completion" />-->
<!-- <meta-data android:name="com.snapchat.kit.sdk.scopes" android:resource="@array/snap_connect_scopes" />-->
<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"
/>
</provider>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -8,7 +8,7 @@ import kotlinx.android.synthetic.main.activity_add_social.*
import kotlinx.android.synthetic.main.content_add_social.*
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.utils.SocialPlatformsManager
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithImageRecycler
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
class AddSocialActivity : AppCompatActivity() {
@ -23,33 +23,33 @@ class AddSocialActivity : AppCompatActivity() {
val loggedInSocials = SocialPlatformsManager.getLoggedIn(this)
val recyclers = mutableListOf(availableSocialsRecyclerView, addSocialRecyclerView)
availableSocialsRecyclerView.apply {
layoutManager = LinearLayoutManager(this@AddSocialActivity)
adapter = SocialWithImageRecycler(
loggedInSocials,
R.drawable.ic_remove_circle
) {
it.logout(context)
loggedInSocials.remove(it)
notLoggedInSocials.add(it)
recyclers.forEach { it.adapter?.notifyDataSetChanged() }
Toast.makeText(context, "Logged out from ${it.getName()}!", Toast.LENGTH_SHORT).show()
}
}
addSocialRecyclerView.apply {
layoutManager = LinearLayoutManager(this@AddSocialActivity)
adapter = SocialWithImageRecycler(
notLoggedInSocials,
R.drawable.ic_add_circle
) {
it.login(context)
notLoggedInSocials.remove(it)
loggedInSocials.add(it)
recyclers.forEach { it.adapter?.notifyDataSetChanged() }
Toast.makeText(context, "Logged in to ${it.getName()}!", Toast.LENGTH_SHORT).show()
}
}
// availableSocialsRecyclerView.apply {
// layoutManager = LinearLayoutManager(this@AddSocialActivity)
// adapter = SocialWithButtonRecycler(
// loggedInSocials,
// R.mipmap.ic_opaque_remove
// ) {
// it.logout(context)
// loggedInSocials.remove(it)
// notLoggedInSocials.add(it)
// recyclers.forEach { it.adapter?.notifyDataSetChanged() }
// Toast.makeText(context, "Logged out from ${it.getName()}!", Toast.LENGTH_SHORT).show()
// }
// }
//
// addSocialRecyclerView.apply {
// layoutManager = LinearLayoutManager(this@AddSocialActivity)
// adapter = SocialWithButtonRecycler(
// notLoggedInSocials,
// R.mipmap.ic_opaque_add
// ) {
// it.login(context)
// notLoggedInSocials.remove(it)
// loggedInSocials.add(it)
// recyclers.forEach { it.adapter?.notifyDataSetChanged() }
// Toast.makeText(context, "Logged in to ${it.getName()}!", Toast.LENGTH_SHORT).show()
// }
// }
}

View File

@ -5,17 +5,21 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.jakewharton.rxbinding2.view.RxView
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.activity_main.*
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.utils.SocialPlatformsManager
import pl.edu.amu.wmi.socialaggregator.viewholders.PostTextRecycler
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithImageRecycler
import pl.edu.amu.wmi.socialaggregator.viewholders.PostSummaryRecycler
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
class MainActivity : AppCompatActivity() {
private val subs = CompositeDisposable()
private val TAG = MainActivity::class.java.simpleName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@ -26,13 +30,18 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(this, NewPostActivity::class.java)
startActivity(intent)
},
RxView.clicks(connectedSocialsButton)
RxView.clicks(previousPostsButton)
.subscribe {
val intent = Intent(this, AddSocialActivity::class.java)
val intent = Intent(this, PostHistoryActivity::class.java)
startActivity(intent)
}
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
SocialPlatformsManager.getAll()
.forEach { it.onActivityResult(requestCode, resultCode, data) }
super.onActivityResult(requestCode, resultCode, data)
}
override fun onResume() {
@ -40,14 +49,20 @@ class MainActivity : AppCompatActivity() {
connectedSocialsRecyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = SocialWithImageRecycler(SocialPlatformsManager.getLoggedIn(this@MainActivity),
R.drawable.ic_launcher_background) {}
adapter =
SocialWithButtonRecycler(SocialPlatformsManager.getAll(), this@MainActivity)
}
previousPostsRecyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = PostTextRecycler(SocialPlatformsManager.getLoggedIn(this@MainActivity)
.map { it to it.getPosts(this@MainActivity).size })
adapter = PostSummaryRecycler(
Observable.merge(SocialPlatformsManager.getLoggedIn(this@MainActivity)
.map { social ->
social.getPosts(this@MainActivity)
.map { social to it }
.toObservable()
})
)
}
}

View File

@ -1,20 +1,41 @@
package pl.edu.amu.wmi.socialaggregator.activity
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import android.provider.MediaStore
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding2.view.RxView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import kotlinx.android.synthetic.main.activity_new_post.*
import kotlinx.android.synthetic.main.content_new_post.*
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
import pl.edu.amu.wmi.socialaggregator.utils.SocialPlatformsManager
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithToggleRecycler
class NewPostActivity : AppCompatActivity() {
var file: String? = null
val postedSubject = PublishSubject.create<Any>()
val resumedSubject = PublishSubject.create<Any>()
@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -36,16 +57,115 @@ class NewPostActivity : AppCompatActivity() {
}
RxView.clicks(publishPost)
.subscribe {
availablesRecycler.chips.entries.forEach { (social, chip) ->
if (chip.isChecked) {
social.addPost(this, postText.text?.toString() ?: "test", emptyList())
Toast.makeText(this, "Posted to ${social.getName()}!", Toast.LENGTH_LONG)
.show()
}
.filter { file != null }
.doOnNext {
val sdk = android.os.Build.VERSION.SDK_INT
if (sdk < android.os.Build.VERSION_CODES.HONEYCOMB) {
val clipboard =
getSystemService(Context.CLIPBOARD_SERVICE) as android.text.ClipboardManager
clipboard.text = postText.text?.toString() ?: ""
} else {
val clipboard =
getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clip = android.content.ClipData.newPlainText(
"text label",
postText.text?.toString() ?: ""
)
clipboard.primaryClip = clip
}
finish()
}
.observeOn(Schedulers.io())
.doOnNext {
resumedSubject
.zipWith(
Observable.fromIterable(availablesRecycler.chips.entries
.filter { (_, chip) -> chip.isChecked }
.map { it.key }),
BiFunction<Any, SocialPlatform, SocialPlatform> { _, t2 -> t2 }
)
.observeOn(Schedulers.io())
.doOnNext {
val bitmap = BitmapFactory.decodeFile(file!!)
it.addPost(
postedSubject, this,
postText.text?.toString() ?: "",
listOf(bitmap),
listOf(file!!)
)
}
.take(availablesRecycler.chips.count { it.value.isChecked }.toLong() + 1)
.toList()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { _ ->
finish()
}
}
.subscribe {
resumedSubject.onNext(Any())
}
RxView.clicks(imageView2)
.subscribe {
val getIntent = Intent(Intent.ACTION_GET_CONTENT)
getIntent.type = "image/*"
val pickIntent = Intent(
Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
)
pickIntent.type = "image/*"
val chooserIntent = Intent.createChooser(getIntent, "Select Image")
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pickIntent))
startActivityForResult(chooserIntent, PICK_IMAGE_REQUEST_CODE)
}
if (canAccessGallery()) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 0
)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
SocialPlatformsManager.getAll()
.forEach { it.onActivityResult(requestCode, resultCode, data) }
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PICK_IMAGE_REQUEST_CODE) {
data?.data?.let { uri ->
file = getPath(uri)
imageView2.setImageURI(uri)
}
}
}
fun getPath(uri: Uri): String? {
val projection = arrayOf(MediaStore.Images.Media.DATA)
val cursor = contentResolver.query(uri, projection, null, null, null) ?: return null
val column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
cursor.moveToFirst()
val s = cursor.getString(column_index)
cursor.close()
return s
}
override fun onResume() {
super.onResume()
resumedSubject.onNext(Any())
}
private fun canAccessGallery() =
(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)
companion object {
const val PICK_IMAGE_REQUEST_CODE = 69
}
}

View File

@ -0,0 +1,41 @@
package pl.edu.amu.wmi.socialaggregator.activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import io.reactivex.Observable
import kotlinx.android.synthetic.main.activity_post_history.*
import kotlinx.android.synthetic.main.content_post_history.*
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.utils.SocialPlatformsManager
import pl.edu.amu.wmi.socialaggregator.viewholders.PostDetailsRecycler
import androidx.recyclerview.widget.DividerItemDecoration
class PostHistoryActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_post_history)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
postHistoryRecycler.apply {
layoutManager = LinearLayoutManager(this@PostHistoryActivity)
adapter = PostDetailsRecycler(
Observable.fromIterable(SocialPlatformsManager.getLoggedIn(this@PostHistoryActivity))
.flatMap {
it.getPosts(this@PostHistoryActivity)
.toObservable()
})
addItemDecoration(
DividerItemDecoration(
context,
(layoutManager as LinearLayoutManager).orientation
))
}
}
}

View File

@ -0,0 +1,234 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.facebook.*
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult
import com.facebook.login.widget.LoginButton
import com.facebook.share.Sharer
import com.facebook.share.model.ShareMediaContent
import com.facebook.share.model.SharePhoto
import com.facebook.share.widget.ShareDialog
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import org.json.JSONArray
import org.json.JSONObject
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.utils.Utils
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
class Facebook : SocialPlatform {
private var callbackManager: CallbackManager = CallbackManager.Factory.create()
override fun getName(): String = "Facebook"
override fun getLogo(): Int = R.mipmap.ic_logo_facebook
override fun login(context: Context) {
}
override fun logout(context: Context) {
}
override fun isLoggedIn(context: Context): Boolean {
val accessToken = AccessToken.getCurrentAccessToken()
return accessToken != null && !accessToken.isExpired
}
override fun addPost(
publishSubject: PublishSubject<Any>,
context: Context,
text: String,
images: List<Bitmap>,
imagePaths: List<String>
) {
val content = ShareMediaContent.Builder().apply {
images.forEach {
addMedium(
SharePhoto.Builder().setBitmap(it).build()
)
}
}.build()
val shareDialog = ShareDialog(context as Activity)
shareDialog.registerCallback(callbackManager, object : FacebookCallback<Sharer.Result?> {
override fun onSuccess(result: Sharer.Result?) {
publishSubject.onNext(Any())
}
override fun onCancel() {
publishSubject.onNext(Any())
}
override fun onError(error: FacebookException?) {
publishSubject.onError(Exception(error?.localizedMessage))
}
})
shareDialog.show(content, ShareDialog.Mode.AUTOMATIC)
}
override fun getPosts(context: Context): Single<List<Post>> {
return Single.just(AccessToken.getCurrentAccessToken())
.observeOn(Schedulers.io())
.map { token ->
if (token.isExpired) {
return@map emptyList<Post>()
} else {
val request = GraphRequest.newGraphPathRequest(
AccessToken.getCurrentAccessToken(),
"/me/posts"
) {
Log.i(getName(), it.toString())
}
val parameters = Bundle()
parameters.putString(
"fields",
"likes.summary(true),created_time,message,attachments{url,unshimmed_url,media,subattachments}"
)
request.parameters = parameters
val res = request.executeAndWait()
val data = res.jsonObject["data"] as JSONArray
val size = data.length()
return@map (0 until size).map {
val obj = data[it] as JSONObject
val msg = if (obj.has("message")) obj.getString("message") else ""
val date =
if (obj.has("created_time")) obj.getString("created_time") else ""
// SimpleDateFormat("YYYY-MM-DD\'T\'hh:mm:ssZ").parse(date)
Post(this, msg, date, getImages(obj))
}
}
}
.observeOn(AndroidSchedulers.mainThread())
}
private fun getImages(obj: JSONObject): List<String> {
val list = emptyList<String>().toMutableList()
if (obj.has("attachments")) {
val attachments = obj.getJSONObject("attachments").getJSONArray("data")
try {
list.addAll((0 until attachments.length())
.map { attachments[it] as JSONObject }
.flatMap {
val urls = mutableListOf<String>()
if (it.has("media")) {
urls.add(
it.getJSONObject("media")
.getJSONObject("image")
.getString("src")
)
}
if (it.has("subattachments")) {
val subattachments =
it.getJSONObject("subattachments").getJSONArray("data")
urls.addAll((0 until subattachments.length())
.map { subattachments[it] as JSONObject }
.map {
it.getJSONObject("media")
.getJSONObject("image")
.getString("src")
})
}
return@flatMap urls
})
} catch (e: Exception) {
Log.e(getName(), e.toString())
}
}
return list
}
override fun handleButtonView(context: Context): (SocialWithButtonRecycler.ViewHolder) -> Unit =
{ viewHolder ->
viewHolder.textView.text = this.getName()
val constraintSet = ConstraintSet()
val button = LoginButton(context)
button.id = View.generateViewId()
val parent = viewHolder.image.parent as ViewGroup
parent.removeView(viewHolder.image)
parent.addView(button)
constraintSet.clone(parent as ConstraintLayout)
constraintSet.connect(
button.id, ConstraintSet.TOP,
parent.id, ConstraintSet.TOP, 8.toPx(context)
)
constraintSet.connect(
button.id, ConstraintSet.END,
parent.id, ConstraintSet.END
)
constraintSet.connect(
button.id, ConstraintSet.BOTTOM,
parent.id, ConstraintSet.BOTTOM, 8.toPx(context)
)
constraintSet.connect(
viewHolder.textView.id, ConstraintSet.BOTTOM,
button.id, ConstraintSet.BOTTOM
)
constraintSet.connect(
viewHolder.textView.id, ConstraintSet.END,
button.id, ConstraintSet.START, 8.toPx(context)
)
constraintSet.connect(
viewHolder.textView.id, ConstraintSet.START,
parent.id, ConstraintSet.START
)
constraintSet.connect(
viewHolder.textView.id, ConstraintSet.TOP,
button.id, ConstraintSet.TOP
)
constraintSet.setHorizontalBias(viewHolder.textView.id, 0.toFloat())
constraintSet.applyTo(parent)
button.setPermissions(listOf("email"))
val callback = object : FacebookCallback<LoginResult> {
override fun onSuccess(result: LoginResult?) {
Log.i(getName(), "SUCCESS!")
LoginManager.getInstance().logInWithReadPermissions(
context as Activity,
listOf("email")
)
}
override fun onCancel() {
Log.i(getName(), "CANCEL!")
}
override fun onError(error: FacebookException?) {
Log.i(getName(), "FAIL!")
}
}
button.registerCallback(callbackManager, callback)
LoginManager.getInstance().registerCallback(callbackManager, callback)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
callbackManager.onActivityResult(requestCode, resultCode, data)
}
private fun Int.toPx(context: Context): Int {
return Utils.convertDpToPixel(this.toFloat(), context).toInt()
}
}

View File

@ -1,65 +0,0 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import pl.edu.amu.wmi.socialaggregator.utils.InternalStorage
import java.io.ByteArrayOutputStream
import java.io.File
class FacebookMock : SocialPlatform {
companion object {
val TAG = FacebookMock::class.java.canonicalName
}
override fun getName(): String = "Facebook"
override fun login(context: Context) {
val loginsDir = InternalStorage.getFileOrDir(context, "logins")
if (loginsDir != null) {
val loginFile = File(loginsDir, "facebook")
loginFile.createNewFile()
} else {
Log.e(TAG, "Could not create logins directory")
}
}
override fun logout(context: Context) {
InternalStorage.getFileOrDir(context, "logins/facebook")?.delete()
}
override fun isLoggedIn(context: Context): Boolean {
return InternalStorage.getFileOrDir(context, "logins/facebook")?.exists() ?: false
}
override fun addPost(context: Context, text: String, images: List<Bitmap>) {
val postsDir = InternalStorage.getFileOrDir(context, "posts/facebook")
if (postsDir != null) {
val postDir = File(postsDir, System.currentTimeMillis().toString())
postDir.mkdirs()
val textFile = File(postDir, "content")
textFile.createNewFile()
textFile.writeText(text)
images.forEachIndexed { index, image ->
val imageFile = File(postDir, "image$index")
imageFile.createNewFile()
ByteArrayOutputStream().use { stream ->
image.compress(Bitmap.CompressFormat.JPEG, 100, stream)
imageFile.writeBytes(stream.toByteArray())
}
}
} else {
Log.e(TAG, "Could not create posts directory")
}
}
override fun getPosts(context: Context): List<String> {
val postsDir = InternalStorage.getFileOrDir(context, "posts/facebook")
return postsDir?.listFiles()?.map { it.name }?.toList() ?: emptyList()
}
}

View File

@ -0,0 +1,5 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
import java.lang.Exception
class NotApplicableException(val socialPlatform: SocialPlatform) : Exception()

View File

@ -0,0 +1,8 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
data class Post(
val social: SocialPlatform,
val content: String,
val dateTime: String,
val images: List<String>
)

View File

@ -0,0 +1,83 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.util.Log
import com.snapchat.kit.sdk.SnapCreative
import com.snapchat.kit.sdk.creative.api.SnapCreativeKitCompletionCallback
import com.snapchat.kit.sdk.creative.api.SnapCreativeKitSendError
import com.snapchat.kit.sdk.creative.exceptions.SnapMediaSizeException
import com.snapchat.kit.sdk.creative.models.SnapPhotoContent
import io.reactivex.Single
import io.reactivex.subjects.PublishSubject
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
import java.io.File
import java.lang.Exception
class Snapchat : SocialPlatform {
override fun getName(): String = "Snapchat"
override fun login(context: Context) {
}
override fun logout(context: Context) {
}
override fun isLoggedIn(context: Context): Boolean = true
override fun addPost(
publishSubject: PublishSubject<Any>,
context: Context,
text: String,
images: List<Bitmap>,
imagePaths: List<String>
) {
val snapCreativeKitApi = SnapCreative.getApi(context)
val content = imagePaths.map {
val snapMediaFactory = SnapCreative.getMediaFactory(context)
val photoFile = try {
snapMediaFactory.getSnapPhotoFromFile(File(it))
} catch (e: SnapMediaSizeException) {
Log.e(getName(), e.toString())
null
}
SnapPhotoContent(photoFile!!)
}.first()
if (text.isNotBlank()) {
content.captionText = text
}
snapCreativeKitApi.sendWithCompletionHandler(
content,
object : SnapCreativeKitCompletionCallback {
override fun onSendSuccess() {
publishSubject.onNext(Any())
}
override fun onSendFailed(p0: SnapCreativeKitSendError?) {
publishSubject.onError(Exception("Snapchat error"))
}
})
}
override fun getPosts(context: Context): Single<List<Post>> {
return Single.just(emptyList())
}
override fun getLogo(): Int = R.drawable.ic_icon_snapchat
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
}
override fun handleButtonView(context: Context): (SocialWithButtonRecycler.ViewHolder) -> Unit =
{
it.image.setImageResource(getLogo())
it.textView.text = getName()
}
}

View File

@ -1,7 +1,11 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import io.reactivex.Single
import io.reactivex.subjects.PublishSubject
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
interface SocialPlatform {
@ -9,6 +13,12 @@ interface SocialPlatform {
fun login(context: Context)
fun logout(context: Context)
fun isLoggedIn(context: Context): Boolean
fun addPost(context: Context, text: String, images: List<Bitmap>)
fun getPosts(context: Context): List<String>
fun addPost(publishSubject: PublishSubject<Any>,
context: Context, text: String, images: List<Bitmap>,
imagePaths: List<String>)
fun getPosts(context: Context): Single<List<Post>>
fun getLogo(): Int
fun handleButtonView(context: Context): (SocialWithButtonRecycler.ViewHolder) -> Unit = {}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {}
}

View File

@ -1,14 +1,19 @@
package pl.edu.amu.wmi.socialaggregator.utils
import android.content.Context
import pl.edu.amu.wmi.socialaggregator.socialplatforms.FacebookMock
import pl.edu.amu.wmi.socialaggregator.socialplatforms.Facebook
import pl.edu.amu.wmi.socialaggregator.socialplatforms.Snapchat
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
object SocialPlatformsManager {
private val IMPLEMENTED_PLATFORMS = listOf<SocialPlatform>(
FacebookMock()
private val IMPLEMENTED_PLATFORMS = listOf(
// FacebookMock()
Facebook(),
Snapchat()
)
fun getAll() = IMPLEMENTED_PLATFORMS
fun getLoggedIn(context: Context) =
IMPLEMENTED_PLATFORMS.filter { it.isLoggedIn(context) }.toMutableList()

View File

@ -0,0 +1,12 @@
package pl.edu.amu.wmi.socialaggregator.utils
import android.content.Context
import android.util.DisplayMetrics
object Utils {
fun convertDpToPixel(dp: Float, context: Context): Float {
return dp * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)
}
}

View File

@ -0,0 +1,105 @@
package pl.edu.amu.wmi.socialaggregator.viewholders
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.Picasso
import io.reactivex.Observable
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.socialplatforms.NotApplicableException
import pl.edu.amu.wmi.socialaggregator.socialplatforms.Post
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
@SuppressLint("CheckResult")
class PostDetailsRecycler(
postsObservable: Observable<List<Post>>
) : RecyclerView.Adapter<PostDetailsRecycler.ViewHolder>() {
val posts = emptyList<Post>().toMutableList()
init {
postsObservable
.subscribe {
posts.addAll(it)
notifyDataSetChanged()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_post, parent, false) as ConstraintLayout
val dateTime = layout.findViewById<TextView>(R.id.postDateTimeTextView)
val postContent = layout.findViewById<TextView>(R.id.postContentTextView)
val imageView = layout.findViewById<ImageView>(R.id.postSocialImage)
val imagesContainer = layout.findViewById<LinearLayout>(R.id.imagesContainer)
return ViewHolder(layout, dateTime, postContent, imageView, imagesContainer)
}
override fun getItemCount(): Int {
return posts.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val post = posts[position]
holder.dateTime.text = formatDate(post.dateTime)
holder.postContent.text = post.content
holder.postContent.visibility = if (post.content.isBlank()) View.GONE else View.VISIBLE
holder.imageView.setImageResource(post.social.getLogo())
holder.imagesContainer.apply {
removeAllViews()
post {
val imgWidth = (parent as ConstraintLayout).width / post.images.size - 10
post.images.forEach {
val imageView = ImageView(context)
addView(imageView)
imageView.apply {
(layoutParams as ViewGroup.MarginLayoutParams)
.apply {
marginEnd = 10
}
}
Picasso.with(context)
.load(it)
.placeholder(android.R.drawable.ic_menu_gallery)
.resize(imgWidth, imgWidth)
.centerCrop()
.into(imageView)
}
}
}
}
private fun formatDate(str: String): String {
val regex = """(\d+-\d+-\d+)T(\d+:\d+:\d+)""".toRegex()
val matchResult = regex.find(str)
val (date, time) = matchResult!!.destructured
return "$date $time"
}
class ViewHolder(
root: ConstraintLayout,
val dateTime: TextView,
val postContent: TextView,
val imageView: ImageView,
val imagesContainer: LinearLayout
) : RecyclerView.ViewHolder(root)
}

View File

@ -0,0 +1,65 @@
package pl.edu.amu.wmi.socialaggregator.viewholders
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable
import io.reactivex.Single
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.socialplatforms.NotApplicableException
import pl.edu.amu.wmi.socialaggregator.socialplatforms.Post
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
@SuppressLint("CheckResult")
class PostSummaryRecycler(
socialsObservable: Observable<Pair<SocialPlatform, List<Post>?>>
) : RecyclerView.Adapter<PostSummaryRecycler.ViewHolder>() {
private val socials = mutableListOf<Pair<SocialPlatform, List<Post>?>>()
init {
socialsObservable
.doOnNext {
socials.add(it)
notifyDataSetChanged()
}
.subscribe()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.previous_posts, parent, false) as ConstraintLayout
val socialName = layout.findViewById<TextView>(R.id.socialTextView)
val postCount = layout.findViewById<TextView>(R.id.post)
val imageView = layout.findViewById<ImageView>(R.id.postSocialImage)
return ViewHolder(layout, socialName, postCount, imageView)
}
override fun getItemCount(): Int {
return socials.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val (social, posts) = socials[position]
val count = posts?.size
holder.socialName.text = social.getName()
holder.postCount.text = count?.let {
count.toString() + if (count > 1) " posts" else " post"
} ?: "Not applicable"
holder.imageView.setImageResource(social.getLogo())
}
class ViewHolder(
root: ConstraintLayout,
val socialName: TextView,
val postCount: TextView,
val imageView: ImageView
) : RecyclerView.ViewHolder(root)
}

View File

@ -1,44 +0,0 @@
package pl.edu.amu.wmi.socialaggregator.viewholders
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.jakewharton.rxbinding2.view.RxView
import io.reactivex.disposables.Disposable
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
class PostTextRecycler(
val socials: List<Pair<SocialPlatform, Int>>) : RecyclerView.Adapter<PostTextRecycler.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layout = LayoutInflater.from(parent.context)
.inflate(R.layout.previous_posts, parent, false) as ConstraintLayout
val socialName = layout.findViewById<TextView>(R.id.socialTextView)
val postCount = layout.findViewById<TextView>(R.id.postCountTextView)
return ViewHolder(layout, socialName, postCount)
}
override fun getItemCount(): Int {
return socials.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val pair = socials[position]
holder.socialName.text = pair.first.getName()
val count = pair.second
holder.postCount.text = count.toString() + if (count > 1) " posts" else " post"
}
class ViewHolder(
root: ConstraintLayout,
val socialName: TextView,
val postCount: TextView
) : RecyclerView.ViewHolder(root)
}

View File

@ -1,5 +1,6 @@
package pl.edu.amu.wmi.socialaggregator.viewholders
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
@ -11,11 +12,10 @@ import io.reactivex.disposables.Disposable
import pl.edu.amu.wmi.socialaggregator.R
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
class SocialWithImageRecycler(
class SocialWithButtonRecycler(
val availableSocials: List<SocialPlatform>,
private val imageResource: Int,
private val action: (SocialPlatform) -> Unit
) : RecyclerView.Adapter<SocialWithImageRecycler.ViewHolder>() {
val context: Context
) : RecyclerView.Adapter<SocialWithButtonRecycler.ViewHolder>() {
private val disposables = HashMap<SocialPlatform, Disposable>()
@ -34,18 +34,18 @@ class SocialWithImageRecycler(
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val social = availableSocials[position]
holder.text.text = social.getName()
holder.image.setImageResource(imageResource)
disposables[social] = RxView.clicks(holder.image)
.map { social }
.subscribe(action)
availableSocials[position].handleButtonView(context)(holder)
// holder.textView.text = social.getName()
// holder.image.setImageResource(imageResource ?: social.getLogo())
//
// disposables[social] = RxView.clicks(holder.image)
// .map { social }
// .subscribe(action)
}
class ViewHolder(
root: ConstraintLayout,
val text: TextView,
val textView: TextView,
val image: ImageView
) : RecyclerView.ViewHolder(root)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -14,7 +14,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@mipmap/ic_launcher" />
app:srcCompat="@drawable/logo" />
<TextView
android:id="@+id/connectedSocialsTextView"
@ -22,14 +22,14 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="Connected Socials"
android:text="Available Socials"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/connectedSocialsRecyclerView"
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_height="150dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
@ -41,17 +41,6 @@
</androidx.recyclerview.widget.RecyclerView>
<Button
android:id="@+id/connectedSocialsButton"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="Connect new"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/connectedSocialsRecyclerView" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
@ -63,7 +52,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/connectedSocialsButton" />
app:layout_constraintTop_toBottomOf="@+id/connectedSocialsRecyclerView" />
<TextView
android:id="@+id/textView2"
@ -129,11 +118,12 @@
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:tint="#00FFFFFF"
android:tint="@android:color/background_light"
app:fabSize="auto"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/previousPostsButton"
app:srcCompat="@android:drawable/ic_input_add" />
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@mipmap/ic_opaque_add" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.PostHistoryActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_post_history" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -61,10 +61,11 @@
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:clickable="true"
android:tint="@android:color/background_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/availableSocials"
app:srcCompat="@drawable/ic_add_circle" />
app:srcCompat="@mipmap/ic_opaque_send" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,23 @@
<?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:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".activity.PostHistoryActivity"
tools:showIn="@layout/activity_post_history">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/postHistoryRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,58 @@
<?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/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/postContentTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="TextView"
app:layout_constraintEnd_toStartOf="@+id/postSocialImage"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postDateTimeTextView"
tools:visibility="visible" />
<ImageView
android:id="@+id/postSocialImage"
android:layout_width="@dimen/social_logo_size"
android:layout_height="@dimen/social_logo_size"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@mipmap/ic_logo_facebook" />
<TextView
android:id="@+id/postDateTimeTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="TextView"
android:textSize="10sp"
app:layout_constraintEnd_toStartOf="@+id/postSocialImage"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/imagesContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postContentTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -13,32 +13,32 @@
android:layout_marginEnd="8dp"
android:text="Bla bla bla"
android:textColor="@android:color/primary_text_light"
app:layout_constraintBottom_toTopOf="@+id/postCountTextView"
app:layout_constraintEnd_toStartOf="@+id/imageView3"
app:layout_constraintBottom_toTopOf="@+id/post"
app:layout_constraintEnd_toStartOf="@+id/postSocialImage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/imageView3"
app:layout_constraintTop_toTopOf="@+id/postSocialImage"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/postCountTextView"
android:id="@+id/post"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="Bla bla bla"
android:textColor="@android:color/secondary_text_light"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/imageView3"
app:layout_constraintEnd_toStartOf="@+id/postSocialImage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/socialTextView" />
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/postSocialImage"
android:layout_width="@dimen/social_logo_size"
android:layout_height="@dimen/social_logo_size"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@mipmap/ic_launcher_round" />
app:srcCompat="@mipmap/ic_logo_facebook" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -9,23 +9,23 @@
android:id="@+id/socialPlatformName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="@+id/socialPlatformImage"
app:layout_constraintEnd_toStartOf="@+id/socialPlatformImage"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintTop_toTopOf="@+id/socialPlatformImage"
tools:text="Some Sample Text" />
<ImageView
android:id="@+id/socialPlatformImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="-4dp"
android:layout_marginBottom="-4dp"
app:layout_constraintBottom_toBottomOf="@+id/socialPlatformName"
android:layout_width="@dimen/social_logo_size"
android:layout_height="@dimen/social_logo_size"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/socialPlatformName"
tools:srcCompat="@mipmap/ic_launcher_round" />
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@mipmap/ic_logo_facebook" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#4267b2</color>
<color name="colorPrimaryDark">#4267b2</color>
<color name="colorAccent">#4267b2</color>
<color name="colorPrimary">#512DA8</color>
<color name="colorAccent">#5E35B1</color>
<color name="colorPrimaryDark">#4A148C</color>
</resources>

View File

@ -1,3 +1,4 @@
<resources>
<dimen name="fab_margin">16dp</dimen>
<dimen name="social_logo_size">32dp</dimen>
</resources>

View File

@ -1,5 +1,15 @@
<resources>
<string name="app_name">Social Aggregator</string>
<string name="title_activity_new_post">NewPostActivity</string>
<string name="title_activity_new_post">Create new post!</string>
<string name="title_activity_add_social">AddSocialActivity</string>
<string name="title_activity_post_history">Posts history</string>
<string name="facebook_app_id">2437098899888167</string>
<string name="fb_login_protocol_scheme">fb2437098899888167</string>
<!-- <key>SCSDKScopes</key>-->
<!-- <array name="snap_connect_scopes">-->
<!-- <string>https://auth.snapchat.com/oauth2/api/user.bitmoji.avatar</string>-->
<!-- &lt;!&ndash; other scopes you might have... &ndash;&gt;-->
<!-- </array>-->
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<root-path name="root" path="." />
<external-cache-path name="external_files" path="."/>
<external-path name="external_files" path="."/>
</paths>

View File

@ -19,10 +19,28 @@ allprojects {
repositories {
google()
jcenter()
maven {
url "https://storage.googleapis.com/snap-kit-build/maven"
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
ext {
buildToolsVersion = "28.0.3"
androidxLibVersion = "1.0.0"
coreVersion = "1.3.0-alpha03"
extJUnitVersion = "1.1.2-alpha03"
runnerVersion = "1.3.0-alpha03"
rulesVersion = "1.3.0-alpha03"
espressoVersion = "3.3.0-alpha03"
extJUnitVersion = "1.1.2-alpha03"
extTruthVersion = "1.3.0-alpha03"
robolectricVersion = "4.3.1"
}

View File

@ -19,3 +19,4 @@ android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
android.enableUnitTestBinaryResources=true