val constraintSet1 = ConstraintSet()
val constraintSet2 = ConstraintSet()
private fun initAnim(){
//바뀔 layout
constraintSet2.clone(binding.mainConstraint)
constraintSet2.clear(R.id.btn_container,ConstraintSet.TOP)
constraintSet2.clear(R.id.btn_container,ConstraintSet.BOTTOM)
constraintSet2.connect(R.id.btn_container,ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM)
constraintSet2.clear(R.id.top_cover,ConstraintSet.BOTTOM)
constraintSet2.clear(R.id.top_cover,ConstraintSet.TOP)
constraintSet2.connect(R.id.top_cover,ConstraintSet.BOTTOM,ConstraintSet.PARENT_ID,ConstraintSet.TOP)
//원본 layout
constraintSet1.clone(binding.mainConstraint)
constraintSet1.clear(R.id.btn_container,ConstraintSet.BOTTOM)
constraintSet1.clear(R.id.btn_container,ConstraintSet.TOP)
constraintSet1.connect(R.id.btn_container,ConstraintSet.BOTTOM,ConstraintSet.PARENT_ID,ConstraintSet.BOTTOM)
constraintSet1.clear(R.id.top_cover,ConstraintSet.TOP)
constraintSet1.clear(R.id.top_cover,ConstraintSet.BOTTOM)
constraintSet1.connect(R.id.top_cover,ConstraintSet.TOP,ConstraintSet.PARENT_ID,ConstraintSet.TOP)
}
- clone 대상은 parent view의 constraint
- 이전 제약조건을 제거한뒤 ( clear ) 새 제약조건을 준다 ( connect )
var isChange = true
private fun toggleAnim(){
val at = ChangeBounds()
at.addListener(object :Transition.TransitionListener{
override fun onTransitionStart(transition: Transition) {
}
override fun onTransitionEnd(transition: Transition) {
Log.e("asdf","go")
(requireActivity() as TTSMainActivity).toggle()
}
override fun onTransitionCancel(transition: Transition) {}
override fun onTransitionPause(transition: Transition) {}
override fun onTransitionResume(transition: Transition) {}
})
TransitionManager.beginDelayedTransition(binding.mainConstraint,at)
val ctr = if (isChange) constraintSet1 else constraintSet2
ctr.applyTo(binding.mainConstraint)
isChange = !isChange
}
sealed class Result<out T: Any>{
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: String) : Result<Nothing>()
}
- network response object
MusicApiService.kt
public interface MusicApiService{
@GET("/2020-flo/song.json")
suspend fun fetchSong():Response<Song>
}
- 자기 통신에 맞는 uri interface 정의
NetworkContainer.kt
public class NetworkContainer {
private val interceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val client:OkHttpClient by lazy {
OkHttpClient.Builder()
.connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.addNetworkInterceptor(interceptor)
// .addInterceptor(new AddCookiesInterceptor(getApplicationContext()))
// .addInterceptor(new ReceivedCookiesInterceptor(getApplicationContext()))
.build()
}
private val retrofit:Retrofit by lazy{
Retrofit.Builder()
.baseUrl(NetworkContainer.MEDIA_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
fun getApiService():MusicApiService{
return retrofit.create(MusicApiService::class.java)
}
suspend fun <T : Any> safeApiCall(call: suspend () -> Response<T>): Result<T> {
return try {
val myResp = call.invoke()
if (myResp.isSuccessful) {
Result.Success(myResp.body()!!)
} else {
Result.Error(myResp.errorBody()?.string() ?: "Something goes wrong")
}
} catch (e: Exception) {
Result.Error(e.message ?: "Internet error runs")
}
}
public suspend fun fetchSong():Result<Song>{
return this.safeApiCall(call = { getApiService().fetchSong() } )
}
companion object{
val CONNECT_TIMEOUT = 5000L
val WRITE_TIMEOUT = 5000L
val READ_TIMEOUT = 5000L
val MEDIA_URL = "custom url"
}
}
- getApitService()로 미리 정의 해둔 api interface를 전달.
- 컴파일시 정확한 내용 전달을 위해 safeApiCall()로 한번 감싸줌.
- fetchSong 부분이 실제적인 호출 부분.
SongViewModel.kt
class SongViewModel(val networkContainer:NetworkContainer) :ViewModel(){
private val song: MutableLiveData<Song> by lazy {
MutableLiveData<Song>()
}
fun getSong(): LiveData<Song> {
return song
}
val errorMessage: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
fun fetchSong() {
viewModelScope.launch {
val result = networkContainer.fetchSong()
when(result){
is Result.Success ->{
song.postValue(result.data)
}
is Result.Error -> {
errorMessage.postValue(result.exception)
}
}
}
}
}
- ViewModel을 상속받은 클래스로 위에서 정의했던 NetworkContainer가저옴.
- fetchSong()으로 결과값 가저오기.
- when .. is 조합으로 타입 검사후 결과값이 Success인지 Error인지 구분함
var mmList = [
new MyDataa('key1','제목1','values1'),
new MyDataa('key2','제목2','valueqs'),
new MyDataa('key3','제목w 3','valuest'),
new MyDataa('key4','제목4','valu123 es'),
new MyDataa('key5','제목5','valuaa es'),
new MyDataa('key6','제목6','valu des'),
new MyDataa('key7','제목7',['m1','m2','m3']),
new MyDataa('key8','제목8','valuasdes')
]
테이블만들기
//@param stringSelector: #example , listData: [MyDataa,MyDataa,.. ]
function makeTable(stringSelector ,listData){
var d = document.querySelector(stringSelector)
while (d.firstChild) {
d.removeChild(d.firstChild);
}
var colnum = 4;
var tbl = document.createElement('table');
tbl.setAttribute('text-align', 'center')
tbl.style.width = '100%'
tbl.border = 1
var colgroup = document.createElement('colgroup')
var tbody = document.createElement('tbody')
for (var i = 0; i<colnum; i++){
var col = document.createElement('col')
col.attributes.width = '25%'
colgroup.appendChild(col)
}
var tr = document.createElement('tr')
listData.forEach(function(e,i){
var tTD = document.createElement('td')
tTD.appendChild(document.createTextNode(e.title))
var vTD = document.createElement('td')
if (Array.isArray(e.value)){
var ultag = document.createElement('ul')
e.value.forEach((arrayValue)=>{
var litag = document.createElement('li')
litag.appendChild(document.createTextNode(arrayValue))
ultag.appendChild(litag)
})
vTD.appendChild(ultag)
} else {
vTD.appendChild(document.createTextNode(e.value))
}
tr.appendChild(tTD)
tr.appendChild(vTD)
if (e.key === 'key7'||e.key === 'key8'){
vTD.setAttribute('colSpan', '3')
tbody.appendChild(tr)
tr = document.createElement('tr')
} else {
if(i%2 != 0){
tbody.appendChild(tr)
tr = document.createElement('tr')
}
}
})
tbl.appendChild(colgroup)
tbl.appendChild(tbody)
d.appendChild(tbl)
}
- value 값의 object type에 따라 list 타입이면 ul ,li 수직으로 표현함.
//(작성) 제출 api
@RequestMapping(value = "/submitVerification.do", method = RequestMethod.POST, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Object> submitVerification(VerifyReportSubmitDTO formData) {
try {
int fileCount = veriReportService.saveSubmitReportData(formData);
if (fileCount > 0) {
return ResponseEntity.ok()
.body(VerifyResContainer.Success.body("200", "ok"));
}
} catch(Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(VerifyResContainer.Error.errorMsg("저장중 오류가 발생했습니다."));
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(VerifyResContainer.Error.errorMsg("데이터를 찾지 못했습니다."));
}
request vo
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class VerifyReportSubmitDTO implements Serializable{
private static final long serialVersionUID = 1772260604963268207L;
private String project_id;
private int verify_sn;
private int report_degree;
private List<MultipartFile> excel_data;
private List<MultipartFile> raw_data_list;
private String json_oss_data_list;
}
- List multipart 형태와 string 형태가 통신하는 모습.
파일 다운로드
// file_id 기준 존재하는 파일 다운로드
@RequestMapping(value = "/fileDownload.do", method = RequestMethod.GET)
public ResponseEntity<Resource> fileDownload(@RequestParam String file_id) throws IOException {
//파일에 실제 경로를 가저오는 작업
VerifyFileVO vo = veriReportService.getFileVerifyFile(file_id);
String str = VerifyFileVO.getMyFileFullPath(vo);
//파일 생성
File f = new File(str);
InputStream is = FileUtils.openInputStream(f);
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.add(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\"" + f.getName() + "\"");
return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(is));
}