Androidアプリのフィードバック
最近グッドパッチさんから良い感じのサービスが出ているのを発見した。
アプリのフィードバックを素早く投稿して、エンジニアに共有することができる。また投稿はgithubに投稿されissue化されるのでかなり便利。でもAndroidは5.0以上らしい・・・(Baltoを少しだけ触ってみた)
おしい。4系はまだシェア的に10%ほどいて、切れないのでとりあえずSlackにpostするのを作った。
Activityのキャプチャをとって、postするやつだ。
キャプチャしてSlackに投稿する
まずキャプチャする。
fun AppCompatActivity.captureScreen():Bitmap{
val rootView = window.decorView.rootView
val screenView = rootView.getRootView()
screenView.setDrawingCacheEnabled(true)
val bitmap = Bitmap.createBitmap(screenView.getDrawingCache())
screenView.setDrawingCacheEnabled(false)
return bitmap
}
ActivityのExtensionです。
Fragment
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/white"
android:orientation="vertical"
android:clickable="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="17dp"
android:text="*どこからでも送信できます!!"/>
<RadioGroup
android:orientation="horizontal"
android:id="@+id/buttongroup_feedback"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="バグ"
android:id="@+id/radioButton" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="改善"
android:layout_gravity="right" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="イイね!"
android:layout_gravity="center_horizontal" />
<Button
android:id="@+id/button_feedback"
android:layout_marginLeft="50dp"
android:layout_gravity="right"
android:text="送信!!!"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
</RadioGroup>
<EditText
android:padding="10dp"
android:hint="フィードバックを入力してください"
android:layout_width="match_parent"
android:layout_height="100dp"
android:id="@+id/editText_feedback" />
<ImageView
android:id="@+id/imageView_feedback"
android:layout_width="match_parent"
android:layout_height="300dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>
import android.graphics.Bitmap
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import com.google.repacked.apache.commons.io.FileUtils
import jp.co.rarejob.R
import jp.co.rarejob.lib.util.AlertDialogUtil
import okhttp3.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
/**
* Created by kentaro.haneda on 16/12/21.
*/
class FeedbackFragment(var bitmap:Bitmap) : Fragment() {
lateinit var categoryButtonGroup:RadioGroup
lateinit var feedbacEditText: EditText
lateinit var captureImage:ImageView
var selectedCategoryName = "その他"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onDestroy() {
super.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_feedback, container, false)
captureImage = view.findViewById(R.id.imageView_feedback) as ImageView
captureImage.setImageBitmap(bitmap)
(view.findViewById(R.id.buttongroup_feedback) as RadioGroup).setOnCheckedChangeListener { radioGroup, i ->
when(i){
0 -> { selectedCategoryName = "バグ" }
1 -> { selectedCategoryName = "改善" }
2 -> { selectedCategoryName = "イイね" }
}
}
(view.findViewById(R.id.button_feedback) as Button).setOnClickListener {
postToSlack()
}
feedbacEditText = view.findViewById(R.id.editText_feedback) as EditText
feedbacEditText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
(view.findViewById(R.id.button_feedback) as Button).isEnabled = feedbacEditText.text.toString().count() > 0
}
})
return view
}
fun postToSlack(){
val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
val requestBodyBitmap = RequestBody.create(MediaType.parse("image/png"), byteArrayOutputStream.toByteArray())
val service = SlackServiceGenerator.createService(SlackFeedbackAPI::class.java)
// set post file name
val body = MultipartBody.Part.createFormData("file", "feedback_image.png", requestBodyBitmap)
// for slack
// get from here https://XXXXXXXX.slack.com/apps/new/
val token = RequestBody.create(
MediaType.parse("multipart/form-data"), "XXXXXXXXXXXXX")
val channel = RequestBody.create(
MediaType.parse("multipart/form-data"), "feedback")
val title = RequestBody.create(
MediaType.parse("multipart/form-data"), selectedCategoryName)
val comment = RequestBody.create(
MediaType.parse("multipart/form-data"), feedbacEditText.text.toString())
val pretty = RequestBody.create(
MediaType.parse("multipart/form-data"), "1")
// finally, execute the request
val call = service.upload(token, channel, pretty, title, comment, body)
call.enqueue(object : Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>,
response: Response<ResponseBody>) {
Log.v("Upload", "success")
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.e("Upload error:", t.message)
}
})
}
}
interface SlackFeedbackAPI{
@Multipart
@POST("/api/files.upload")
fun upload(@Part("token") version:RequestBody,
@Part("channels") name:RequestBody,
@Part("pretty") category:RequestBody,
@Part("title") title:RequestBody,
@Part("initial_comment") comment:RequestBody,
@Part file: MultipartBody.Part ): Call<ResponseBody>
}
object SlackServiceGenerator {
val API_BASE_URL = "https://slack.com"
private val httpClient = OkHttpClient.Builder()
private val builder = Retrofit.Builder().baseUrl(API_BASE_URL).addConverterFactory(GsonConverterFactory.create())
fun <S> createService(serviceClass: Class<S>): S {
val retrofit = builder.client(httpClient.build()).build()
return retrofit.create(serviceClass)
}
}
retrofit2とokhttpを使っています。トークンは https://XXXXXXXX.slack.com/apps/new/ こんな感じのURLで発行されると思います。
Baltoが4k対応したら、iOSもまとめたい!