This commit is contained in:
Mateusz Hinc 2020-01-06 19:49:59 +01:00
parent 0c8ec7fd28
commit 4c00eaaa1b
14 changed files with 369 additions and 70 deletions

View File

@ -71,4 +71,7 @@ dependencies {
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)'
}

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="pl.edu.amu.wmi.socialaggregator">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@ -43,6 +45,32 @@
<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"/>
</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.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 = SocialWithImageRecycler(
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()
}
}
// 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

@ -2,20 +2,31 @@ package pl.edu.amu.wmi.socialaggregator.activity
import android.content.Intent
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.facebook.CallbackManager
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.login.LoginResult
import com.jakewharton.rxbinding2.view.RxView
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.PostSummaryRecycler
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithImageRecycler
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
import com.facebook.AccessToken
import com.facebook.login.LoginManager
import com.facebook.share.widget.ShareDialog
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,18 +37,42 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(this, NewPostActivity::class.java)
startActivity(intent)
},
RxView.clicks(connectedSocialsButton)
.subscribe {
val intent = Intent(this, AddSocialActivity::class.java)
startActivity(intent)
},
// RxView.clicks(connectedSocialsButton)
// .subscribe {
// val accessToken = AccessToken.getCurrentAccessToken()
// val isLoggedIn = accessToken != null && !accessToken.isExpired
//
// Log.i(TAG, isLoggedIn.toString())
//
// LoginManager.getInstance().logInWithReadPermissions(this, listOf("public_profile"))
//
//// val request = GraphRequest.newMeRequest(
//// accessToken
//// ) { `object`, response ->
//// Log.v("LoginActivity", response.toString())
////
//// // Application code
//// }
//// val parameters = Bundle()
//// parameters.putString("fields", "id,name,email,gender,birthday")
//// request.parameters = parameters
//// request.executeAsync()
// ShareDialog(this).show()
//// val intent = Intent(this, AddSocialActivity::class.java)
//// startActivity(intent)
// },
RxView.clicks(previousPostsButton)
.subscribe {
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() {
@ -45,7 +80,8 @@ class MainActivity : AppCompatActivity() {
connectedSocialsRecyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = SocialWithImageRecycler(SocialPlatformsManager.getLoggedIn(this@MainActivity)) {}
adapter =
SocialWithButtonRecycler(SocialPlatformsManager.getAll(), this@MainActivity)
}
previousPostsRecyclerView.apply {

View File

@ -1,20 +1,31 @@
package pl.edu.amu.wmi.socialaggregator.activity
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.os.Bundle
import android.provider.MediaStore
import android.widget.Toast
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 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.utils.SocialPlatformsManager
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithToggleRecycler
import java.util.*
class NewPostActivity : AppCompatActivity() {
var file: String? = null
@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -35,17 +46,79 @@ class NewPostActivity : AppCompatActivity() {
adapter = availablesRecycler
}
Observable.fromIterable(availablesRecycler.chips.entries)
.filter { file != null }
.filter { (_, chip) -> chip.isChecked }
.flatMap { (social, _) ->
val bitmap = BitmapFactory.decodeFile(file!!)
// Toast.makeText(this, "Posted to ${social.getName()}!", Toast.LENGTH_LONG)
// .show()
social.addPost(this, postText.text?.toString() ?: "test", listOf(bitmap))
}
.take(availablesRecycler.chips.entries.count { (_, chip) -> chip.isChecked }.toLong())
.subscribe {
finish()
}
RxView.clicks(publishPost)
.subscribe {
availablesRecycler.chips.entries.forEach { (social, chip) ->
if (chip.isChecked) {
social.addPost(this, postText.text?.toString() ?: "test", emptyList())
availablesRecycler.chips.entries
.filter { file != null }
.filter { (_, chip) -> chip.isChecked }
.forEach { (social, _) ->
val bitmap = BitmapFactory.decodeFile(file!!)
social.addPost(this, postText.text?.toString() ?: "test", listOf(bitmap))
Toast.makeText(this, "Posted to ${social.getName()}!", Toast.LENGTH_LONG)
.show()
}
}
finish()
}
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?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PICK_IMAGE_REQUEST_CODE) {
data?.data?.let { uri ->
uri.pathSegments?.last()?.let {
file = it
}
imageView2.setImageURI(uri)
}
}
}
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,145 @@
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.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.facebook.CallbackManager
import com.facebook.FacebookCallback
import com.facebook.FacebookException
import com.facebook.login.LoginResult
import com.facebook.login.widget.LoginButton
import pl.edu.amu.wmi.socialaggregator.utils.Utils
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
import com.facebook.AccessToken
import com.facebook.login.LoginManager
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.subjects.PublishSubject
import pl.edu.amu.wmi.socialaggregator.R
import java.lang.Exception
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(context: Context, text: String, images: List<Bitmap>) : PublishSubject<Any> {
val content = ShareMediaContent.Builder().apply {
images.forEach {addMedium(
SharePhoto.Builder().setBitmap(it).build()
)}
}.build()
val publishSubject = PublishSubject.create<Any>()
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.onError(Exception("Cancelled"))
}
override fun onError(error: FacebookException?) {
publishSubject.onError(Exception(error?.localizedMessage))
}
})
shareDialog.show(content, ShareDialog.Mode.AUTOMATIC)
return publishSubject
}
override fun getPosts(context: Context): List<Post> {
return emptyList()
}
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

@ -10,13 +10,13 @@ import java.io.File
import java.text.DateFormat
import java.util.*
class FacebookMock : SocialPlatform {
abstract class FacebookMock : SocialPlatform {
companion object {
val TAG = FacebookMock::class.java.canonicalName
}
override fun getName(): String = "Facebook"
override fun getName(): String = "Facebook Mock"
override fun getLogo(): Int = R.mipmap.ic_logo_facebook

View File

@ -1,7 +1,10 @@
package pl.edu.amu.wmi.socialaggregator.socialplatforms
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import io.reactivex.subjects.PublishSubject
import pl.edu.amu.wmi.socialaggregator.viewholders.SocialWithButtonRecycler
interface SocialPlatform {
@ -9,7 +12,9 @@ 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 addPost(context: Context, text: String, images: List<Bitmap>): PublishSubject<Any>
fun getPosts(context: Context): 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,18 @@
package pl.edu.amu.wmi.socialaggregator.utils
import android.content.Context
import pl.edu.amu.wmi.socialaggregator.socialplatforms.Facebook
import pl.edu.amu.wmi.socialaggregator.socialplatforms.FacebookMock
import pl.edu.amu.wmi.socialaggregator.socialplatforms.SocialPlatform
object SocialPlatformsManager {
private val IMPLEMENTED_PLATFORMS = listOf<SocialPlatform>(
FacebookMock()
// FacebookMock()
Facebook()
)
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

@ -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? = null,
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 ?: social.getLogo())
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)

View File

@ -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="200dp"
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"
@ -134,6 +123,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/previousPostsButton"
app:layout_constraintVertical_bias="1.0"
app:srcCompat="@mipmap/ic_opaque_add" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -27,5 +27,5 @@
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@mipmap/ic_launcher_round" />
tools:srcCompat="@mipmap/ic_logo_facebook" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,4 +3,7 @@
<string name="title_activity_new_post">NewPostActivity</string>
<string name="title_activity_add_social">AddSocialActivity</string>
<string name="title_activity_post_history">PostHistoryActivity</string>
<string name="facebook_app_id">2437098899888167</string>
<string name="fb_login_protocol_scheme">fb2437098899888167</string>
</resources>