diff --git a/app/build.gradle b/app/build.gradle index 98c5b9b..6169ce4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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)' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 252ed8e..53b7386 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/AddSocialActivity.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/AddSocialActivity.kt index f1f2544..2604fcd 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/AddSocialActivity.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/AddSocialActivity.kt @@ -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() +// } +// } } diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt index 5fe6b2f..8854a5a 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/MainActivity.kt @@ -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 { diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt index ecb0e0e..7c4eee2 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/activity/NewPostActivity.kt @@ -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 } } diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt new file mode 100644 index 0000000..fe1b8a7 --- /dev/null +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/Facebook.kt @@ -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) : PublishSubject { + val content = ShareMediaContent.Builder().apply { + images.forEach {addMedium( + SharePhoto.Builder().setBitmap(it).build() + )} + }.build() + + val publishSubject = PublishSubject.create() + + val shareDialog = ShareDialog(context as Activity) + shareDialog.registerCallback(callbackManager, object : FacebookCallback { + 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 { + 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 { + 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() + } +} + diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt index c5b789d..2419705 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/FacebookMock.kt @@ -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 diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt index 8ee7220..8b627eb 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/socialplatforms/SocialPlatform.kt @@ -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) + fun addPost(context: Context, text: String, images: List): PublishSubject fun getPosts(context: Context): List fun getLogo(): Int + fun handleButtonView(context: Context): (SocialWithButtonRecycler.ViewHolder) -> Unit = {} + fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {} } \ No newline at end of file diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/SocialPlatformsManager.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/SocialPlatformsManager.kt index 0b656a4..b7cf0e2 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/SocialPlatformsManager.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/SocialPlatformsManager.kt @@ -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( - FacebookMock() +// FacebookMock() + Facebook() ) + fun getAll() = IMPLEMENTED_PLATFORMS + fun getLoggedIn(context: Context) = IMPLEMENTED_PLATFORMS.filter { it.isLoggedIn(context) }.toMutableList() diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/Utils.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/Utils.kt new file mode 100644 index 0000000..08109b4 --- /dev/null +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/utils/Utils.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/SocialWithImageRecycler.kt b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/SocialWithButtonRecycler.kt similarity index 72% rename from app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/SocialWithImageRecycler.kt rename to app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/SocialWithButtonRecycler.kt index ce26d2c..2d70c36 100644 --- a/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/SocialWithImageRecycler.kt +++ b/app/src/main/java/pl/edu/amu/wmi/socialaggregator/viewholders/SocialWithButtonRecycler.kt @@ -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, - private val imageResource: Int? = null, - private val action: (SocialPlatform) -> Unit -) : RecyclerView.Adapter() { + val context: Context +) : RecyclerView.Adapter() { private val disposables = HashMap() @@ -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) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 315518b..247ad4f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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" /> -