From d356f06ad089e266cb440dcc22e7b8f326c2a693 Mon Sep 17 00:00:00 2001 From: cgspine Date: Fri, 15 May 2020 22:17:02 +0800 Subject: [PATCH] new feature: scheme --- .../qmui/arch/annotation/ActivityScheme.java | 34 ++ .../qmui/arch/annotation/FragmentScheme.java | 37 ++ .../com/qmuiteam/qmui/arch/BaseProcessor.java | 49 +++ .../qmui/arch/LatestVisitProcessor.java | 15 + .../qmuiteam/qmui/arch/SchemeProcessor.java | 388 ++++++++++++++++++ .../com/qmuiteam/qmui/arch/QMUIFragment.java | 53 +-- .../qmui/arch/QMUIFragmentActivity.java | 75 +--- .../arch/QMUISwipeBackActivityManager.java | 25 +- .../java/com/qmuiteam/qmui/arch/Utils.java | 53 +++ .../qmui/arch/scheme/ActivitySchemeItem.java | 84 ++++ .../qmui/arch/scheme/FragmentSchemeItem.java | 152 +++++++ .../QMUIDefaultSchemeFragmentFactory.java | 98 +++++ .../QMUIDefaultSchemeIntentFactory.java | 64 +++ .../scheme/QMUISchemeFragmentFactory.java | 44 ++ .../scheme/QMUISchemeHandleInterpolator.java | 25 ++ .../qmui/arch/scheme/QMUISchemeHandler.java | 202 +++++++++ .../arch/scheme/QMUISchemeIntentFactory.java | 35 ++ .../scheme/QMUISchemeParamValueDecoder.java | 38 ++ .../qmuiteam/qmui/arch/scheme/SchemeItem.java | 132 ++++++ .../qmuiteam/qmui/arch/scheme/SchemeMap.java | 30 ++ .../qmui/arch/scheme/SchemeValue.java | 30 ++ .../qmuidemo/activity/ArchTestActivity.java | 2 + .../qmuidemo/activity/LauncherActivity.java | 14 +- .../QDTabSegmentFixModeFragment.java | 7 + .../components/QDTabSegmentFragment.java | 3 + .../QDTabSegmentScrollableModeFragment.java | 7 + .../QDTabSegmentSpaceWeightFragment.java | 7 + .../fragment/lab/QDSchemeFragment.java | 99 +++++ .../qmuidemo/manager/QDDataManager.java | 2 + .../qmuidemo/manager/QDSchemeManager.java | 68 +++ .../src/main/res/layout/fragment_scheme.xml | 77 ++++ 31 files changed, 1824 insertions(+), 125 deletions(-) create mode 100644 arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/ActivityScheme.java create mode 100644 arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/FragmentScheme.java create mode 100644 arch-compiler/src/main/java/com/qmuiteam/qmui/arch/SchemeProcessor.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/ActivitySchemeItem.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/FragmentSchemeItem.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeFragmentFactory.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeIntentFactory.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeFragmentFactory.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandleInterpolator.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandler.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeIntentFactory.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeParamValueDecoder.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeItem.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeMap.java create mode 100644 arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeValue.java create mode 100644 qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/lab/QDSchemeFragment.java create mode 100644 qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDSchemeManager.java create mode 100644 qmuidemo/src/main/res/layout/fragment_scheme.xml diff --git a/arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/ActivityScheme.java b/arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/ActivityScheme.java new file mode 100644 index 00000000..3de1a6a0 --- /dev/null +++ b/arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/ActivityScheme.java @@ -0,0 +1,34 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.qmuiteam.qmui.arch.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface ActivityScheme { + String name(); + String[] required() default {}; + Class customFactory() default void.class; + String[] keysWithIntValue() default {}; + String[] keysWithBoolValue() default {}; + String[] keysWithLongValue() default {}; + String[] keysWithFloatValue() default {}; + String[] keysWithDoubleValue() default {}; +} diff --git a/arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/FragmentScheme.java b/arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/FragmentScheme.java new file mode 100644 index 00000000..a1d223b7 --- /dev/null +++ b/arch-annotation/src/main/java/com/qmuiteam/qmui/arch/annotation/FragmentScheme.java @@ -0,0 +1,37 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.qmuiteam.qmui.arch.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target(ElementType.TYPE) +public @interface FragmentScheme { + String name(); + Class[] activities(); + String[] required() default {}; + boolean forceNewActivity() default false; + String forceNewActivityKey() default ""; + Class customFactory() default void.class; + String[] keysWithIntValue() default {}; + String[] keysWithBoolValue() default {}; + String[] keysWithLongValue() default {}; + String[] keysWithFloatValue() default {}; + String[] keysWithDoubleValue() default {}; +} diff --git a/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/BaseProcessor.java b/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/BaseProcessor.java index 6deaacef..483e1819 100644 --- a/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/BaseProcessor.java +++ b/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/BaseProcessor.java @@ -1,3 +1,18 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ package com.qmuiteam.qmui.arch; import com.squareup.javapoet.ClassName; @@ -5,12 +20,15 @@ import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.WildcardTypeName; import java.util.List; +import java.util.Map; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -25,14 +43,22 @@ import static javax.lang.model.element.ElementKind.INTERFACE; public abstract class BaseProcessor extends AbstractProcessor { + static final String ACTIVITY_TYPE = "android.app.Activity"; + static final String FRAGMENT_ACTIVITY_TYPE = "androidx.fragment.app.FragmentActivity"; + static final String FRAGMENT_TYPE = "androidx.fragment.app.Fragment"; static final String QMUI_FRAGMENT_ACTIVITY_TYPE = "com.qmuiteam.qmui.arch.QMUIFragmentActivity"; static final String QMUI_FRAGMENT_TYPE = "com.qmuiteam.qmui.arch.QMUIFragment"; static final String QMUI_ACTIVITY_TYPE = "com.qmuiteam.qmui.arch.QMUIActivity"; + static final ClassName QMUIFragmentActivityName = ClassName.get( + "com.qmuiteam.qmui.arch", "QMUIFragmentActivity"); static final ClassName QMUIFragmentName = ClassName.get( "com.qmuiteam.qmui.arch", "QMUIFragment"); static ClassName MapName = ClassName.get("java.util", "Map"); + static ClassName ListName = ClassName.get("java.util", "List"); + static ClassName ArrayMapName = ClassName.get("android.util", "ArrayMap"); + static ClassName ArrayListName = ClassName.get("java.util", "ArrayList"); static ClassName HashMapName = ClassName.get("java.util", "HashMap"); static ClassName IntegerName = ClassName.get("java.lang", "Integer"); static ClassName StringName = ClassName.get("java.lang", "String"); @@ -140,4 +166,27 @@ public abstract class BaseProcessor extends AbstractProcessor { return typeMirror instanceof DeclaredType && ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE; } + + static AnnotationMirror getAnnotationMirror(Element element, Class annotation) { + List list = element.getAnnotationMirrors(); + if (list == null || list.isEmpty()) { + return null; + } + for (AnnotationMirror item : list) { + if (item.getAnnotationType().toString().equals(annotation.getName())) { + return item; + } + } + return null; + } + + static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) { + Map map = annotationMirror.getElementValues(); + for (Map.Entry item : map.entrySet()) { + if (item.getKey().getSimpleName().toString().equals(key)) { + return item.getValue(); + } + } + return null; + } } diff --git a/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/LatestVisitProcessor.java b/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/LatestVisitProcessor.java index af86f6c2..a6917976 100644 --- a/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/LatestVisitProcessor.java +++ b/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/LatestVisitProcessor.java @@ -1,3 +1,18 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ package com.qmuiteam.qmui.arch; import com.google.auto.service.AutoService; diff --git a/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/SchemeProcessor.java b/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/SchemeProcessor.java new file mode 100644 index 00000000..2d8ffe01 --- /dev/null +++ b/arch-compiler/src/main/java/com/qmuiteam/qmui/arch/SchemeProcessor.java @@ -0,0 +1,388 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.qmuiteam.qmui.arch; + +import com.google.auto.service.AutoService; +import com.google.common.collect.Lists; +import com.qmuiteam.qmui.arch.annotation.ActivityScheme; +import com.qmuiteam.qmui.arch.annotation.FragmentScheme; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.MirroredTypesException; +import javax.lang.model.type.TypeMirror; + +@AutoService(Processor.class) +public class SchemeProcessor extends BaseProcessor { + private static String QMUISchemeIntentFactoryType = "com.qmuiteam.qmui.arch.scheme.QMUISchemeIntentFactory"; + private static String QMUISchemeFragmentFactoryType = "com.qmuiteam.qmui.arch.scheme.QMUISchemeFragmentFactory"; + + private static ClassName SchemeMap = ClassName.get( + "com.qmuiteam.qmui.arch.scheme", "SchemeMap"); + private static ClassName SchemeItem = ClassName.get( + "com.qmuiteam.qmui.arch.scheme", "SchemeItem"); + private static ClassName ActivitySchemeItem = ClassName.get( + "com.qmuiteam.qmui.arch.scheme", "ActivitySchemeItem"); + private static ClassName FragmentSchemeItem = ClassName.get( + "com.qmuiteam.qmui.arch.scheme", "FragmentSchemeItem"); + + private static TypeName SchemeItemList = ParameterizedTypeName.get(ListName, SchemeItem); + private static TypeName MapByAction = ParameterizedTypeName.get(MapName, + StringName, SchemeItemList); + private static TypeName MapForSchemeRequired = ParameterizedTypeName.get(ArrayMapName, + StringName, StringName); + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + Set activitySchemes = roundEnv.getElementsAnnotatedWith(ActivityScheme.class); + Set fragmentSchemes = roundEnv.getElementsAnnotatedWith(FragmentScheme.class); + if (activitySchemes.isEmpty() && fragmentSchemes.isEmpty()) { + return true; + } + TypeSpec.Builder classBuilder = TypeSpec + .classBuilder(SchemeMap.simpleName() + "Impl") + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(SchemeMap); + classBuilder.addField(FieldSpec.builder(MapByAction, "mSchemeMap") + .addModifiers(Modifier.PRIVATE) + .build()); + + + MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addStatement("mSchemeMap = new $T<>()", HashMapName); + + + Map> schemeMap = new HashMap<>(); + for (Element element : activitySchemes) { + if (element instanceof TypeElement) { + TypeElement classElement = (TypeElement) element; + TypeMirror elementType = classElement.asType(); + boolean isActivity = isSubtypeOfType(elementType, ACTIVITY_TYPE); + if (!isActivity) { + error(element, "Must annotated on subclasses of Activity"); + } else { + ActivityScheme annotation = element.getAnnotation(ActivityScheme.class); + String name = annotation.name(); + List elements = schemeMap.get(name); + if (elements == null) { + elements = new ArrayList<>(); + schemeMap.put(name, elements); + } + elements.add(new Item(true, classElement, elementType, annotation.required())); + + } + } + } + + for (Element element : fragmentSchemes) { + if (element instanceof TypeElement) { + TypeElement classElement = (TypeElement) element; + TypeMirror elementType = classElement.asType(); + boolean isQMUIFragment = isSubtypeOfType(elementType, QMUI_FRAGMENT_TYPE); + if (!isQMUIFragment) { + error(element, "Must annotated on subclasses of QMUIFragment"); + } else { + FragmentScheme annotation = element.getAnnotation(FragmentScheme.class); + String name = annotation.name(); + List elements = schemeMap.get(name); + if (elements == null) { + elements = new ArrayList<>(); + schemeMap.put(name, elements); + } + elements.add(new Item(false, classElement, elementType, annotation.required())); + + } + } + } + + constructorBuilder.addStatement("$T elements", SchemeItemList); + constructorBuilder.addStatement("$T required = null", MapForSchemeRequired); + for (String key : schemeMap.keySet()) { + List items = schemeMap.get(key); + constructorBuilder.addStatement("elements = new $T<>()", ArrayListName); + items.sort(new Comparator() { + @Override + public int compare(Item item, Item t1) { + int c1 = item.getRequiredCount(); + int c2 = t1.getRequiredCount(); + return Integer.compare(c2, c1); + } + }); + for (Item item : items) { + ClassName elementName = ClassName.get(item.element); + if (item.isActivity) { + ActivityScheme annotation = item.element.getAnnotation(ActivityScheme.class); + AnnotationMirror annotationMirror = getAnnotationMirror(item.element, ActivityScheme.class); + if (annotationMirror == null) { + continue; + } + + appendRequired(constructorBuilder, annotation.required()); + + CodeBlock customFactory = generateCustomFactory(true, annotationMirror); + CodeBlock intParam = generateTypedParams(annotation.keysWithIntValue()); + CodeBlock boolParam = generateTypedParams(annotation.keysWithBoolValue()); + CodeBlock longParam = generateTypedParams(annotation.keysWithLongValue()); + CodeBlock floatParam = generateTypedParams(annotation.keysWithFloatValue()); + CodeBlock doubleParam = generateTypedParams(annotation.keysWithDoubleValue()); + + CodeBlock codeBlock = CodeBlock.builder() + .add("elements.add(") + /**/.add("new $T(", ActivitySchemeItem) + /*---*/.add("$T.class", elementName) + /*---*/.add(",") + /*---*/.add(customFactory) + /*---*/.add(",") + /*---*/.add("required") + /*---*/.add(",") + /*---*/.add(intParam) + /*---*/.add(",") + /*---*/.add(boolParam) + /*---*/.add(",") + /*---*/.add(longParam) + /*---*/.add(",") + /*---*/.add(floatParam) + /*---*/.add(",") + /*---*/.add(doubleParam) + /**/.add(")") + .add(")") + .build(); + constructorBuilder.addStatement(codeBlock); + } else { + FragmentScheme annotation = item.element.getAnnotation(FragmentScheme.class); + AnnotationMirror annotationMirror = getAnnotationMirror(item.element, FragmentScheme.class); + if (annotationMirror == null) { + continue; + } + appendRequired(constructorBuilder, annotation.required()); + CodeBlock customFactory = generateCustomFactory(false, annotationMirror); + CodeBlock activities = generateFragmentHostActivityList(annotation); + CodeBlock intParam = generateTypedParams(annotation.keysWithIntValue()); + CodeBlock boolParam = generateTypedParams(annotation.keysWithBoolValue()); + CodeBlock longParam = generateTypedParams(annotation.keysWithLongValue()); + CodeBlock floatParam = generateTypedParams(annotation.keysWithFloatValue()); + CodeBlock doubleParam = generateTypedParams(annotation.keysWithDoubleValue()); + + CodeBlock codeBlock = CodeBlock.builder() + .add("elements.add(") + /**/.add("new $T(", FragmentSchemeItem) + /*---*/.add("$T.class", elementName) + /*---*/.add(",") + /*---*/.add(activities) + /*---*/.add(",") + /*---*/.add(customFactory) + /*---*/.add(",") + /*---*/.add("$L", annotation.forceNewActivity()) + /*---*/.add(",") + /*---*/.add("$S", annotation.forceNewActivityKey()) + /*---*/.add(",") + /*---*/.add("required") + /*---*/.add(",") + /*---*/.add(intParam) + /*---*/.add(",") + /*---*/.add(boolParam) + /*---*/.add(",") + /*---*/.add(longParam) + /*---*/.add(",") + /*---*/.add(floatParam) + /*---*/.add(",") + /*---*/.add(doubleParam) + /**/.add(")") + .add(")") + .build(); + constructorBuilder.addStatement(codeBlock); + } + } + + constructorBuilder.addStatement("mSchemeMap.put($S, elements)", key); + } + + ExecutableElement findScheme = getOverrideMethod( + SchemeMap, "findScheme"); + List findSchemeParams = findScheme.getParameters(); + String schemeAction = findSchemeParams.get(0).getSimpleName().toString(); + String schemeParam = findSchemeParams.get(1).getSimpleName().toString(); + MethodSpec.Builder getRecordMetaById = MethodSpec.overriding(findScheme) + .addStatement("$T list = mSchemeMap.get($L)", SchemeItemList, schemeAction) + .beginControlFlow("if(list == null || list.isEmpty())") + /**/.addStatement("return null") + .endControlFlow() + .beginControlFlow("for (int i = 0; i < list.size(); i++)") + /**/.addStatement("$T item = list.get(i)", SchemeItem) + /**/.beginControlFlow("if(item.match($L))", schemeParam) + /*--*/.addStatement("return item") + /**/.endControlFlow() + .endControlFlow() + .addStatement("return null"); + ExecutableElement exists = getOverrideMethod( + SchemeMap, "exists"); + MethodSpec.Builder getRecordMetaByClass = MethodSpec.overriding(exists) + .addStatement("return mSchemeMap.containsKey($L)", exists.getParameters().get(0).getSimpleName().toString()); + + classBuilder + .addMethod(constructorBuilder.build()) + .addMethod(getRecordMetaById.build()) + .addMethod(getRecordMetaByClass.build()); + try { + JavaFile.builder(SchemeMap.packageName(), classBuilder.build()) + .build().writeTo(mFiler); + } catch (IOException e) { + error(null, "Unable to generate RecordMetaMapImpl: %s", e.getMessage()); + } + return true; + } + + private void appendRequired(MethodSpec.Builder constructorBuilder, String[] required) { + if (required == null || required.length == 0) { + constructorBuilder.addStatement("required =null"); + return; + } + constructorBuilder.addStatement("required = new $T<>()", ArrayMapName); + for (int i = 0; i < required.length; i++) { + String condition = required[i]; + if (condition == null || condition.isEmpty()) { + continue; + } + int index = condition.indexOf("="); + if (index < 0 || index >= condition.length()) { + constructorBuilder.addStatement("required.put($S, null)", condition); + } else { + String key = condition.substring(0, index); + String value = index == condition.length() - 1 ? "" : condition.substring(index + 1); + constructorBuilder.addStatement("required.put($S, $S)", key, value); + } + } + } + + private CodeBlock generateTypedParams(String[] keys) { + CodeBlock.Builder builder = CodeBlock.builder(); + if (keys == null || keys.length == 0) { + builder.add("null"); + } else { + builder.add("new $T[]{", StringName); + for (int i = 0; i < keys.length; i++) { + if (i != 0) { + builder.add(","); + } + builder.add("$S", keys[i]); + } + builder.add("}"); + } + return builder.build(); + } + + + private CodeBlock generateCustomFactory(boolean isActivity, AnnotationMirror annotationMirror){ + AnnotationValue customFactory = getAnnotationValue(annotationMirror, "customFactory"); + if (customFactory == null) { + return CodeBlock.of("null"); + } + TypeMirror typeMirror = (TypeMirror) customFactory.getValue(); + if(isActivity){ + if (!isSubtypeOfType(typeMirror, QMUISchemeIntentFactoryType)) { + throw new IllegalStateException("customFactory must implement interface QMUISchemeIntentFactory."); + } + }else{ + if (!isSubtypeOfType(typeMirror, QMUISchemeFragmentFactoryType)) { + throw new IllegalStateException("customFactory must implement interface QMUISchemeFragmentFactory."); + } + } + + return CodeBlock.of("$T.class", typeMirror); + } + + private CodeBlock generateFragmentHostActivityList(FragmentScheme fragmentScheme){ + CodeBlock.Builder builder = CodeBlock.builder(); + TypeMirror[] activities = null; + try { + fragmentScheme.activities(); + } catch (MirroredTypesException mte) { + List containerMirrors = mte.getTypeMirrors(); + activities = new TypeMirror[containerMirrors.size()]; + for (int i = 0; i < activities.length; i++) { + activities[i] = containerMirrors.get(i); + } + } + if(activities == null || activities.length == 0){ + throw new IllegalStateException("FragmentScheme#activities can not be empty."); + } + builder.add("new $T[]{", OriginClassName); + for(int i=0; i < activities.length; i++){ + TypeMirror item = activities[i]; + if(!isSubtypeOfType(item, QMUI_FRAGMENT_ACTIVITY_TYPE)){ + throw new IllegalStateException("FragmentScheme#activities must be QMUIFragmentActivity."); + } + if(i > 0){ + builder.add(","); + } + builder.add("$T.class", ClassName.get(item)); + } + builder.add("}"); + return builder.build(); + } + + @Override + public Set getSupportedAnnotationTypes() { + Set types = new LinkedHashSet<>(); + types.add(ActivityScheme.class.getCanonicalName()); + types.add(FragmentScheme.class.getCanonicalName()); + return types; + } + + static class Item { + boolean isActivity = false; + TypeElement element; + TypeMirror type; + String[] required; + + public Item(boolean isActivity, TypeElement element, TypeMirror type, String[] required) { + this.isActivity = isActivity; + this.element = element; + this.type = type; + this.required = required; + } + + int getRequiredCount(){ + return required == null ? 0 : required.length; + } + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java b/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java index 56c0e0b1..0fa4057b 100644 --- a/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragment.java @@ -321,8 +321,8 @@ public abstract class QMUIFragment extends Fragment implements * @param useNewTransitionConfigWhenPop if true, use animation generated by transition C->D, * else, use animation generated by transition B->C */ - protected int startFragmentAndDestroyCurrent(final QMUIFragment fragment, - final boolean useNewTransitionConfigWhenPop) { + protected int startFragmentAndDestroyCurrent(QMUIFragment fragment, + boolean useNewTransitionConfigWhenPop) { if (!checkStateLoss("startFragmentAndDestroyCurrent")) { return -1; } @@ -337,7 +337,7 @@ public abstract class QMUIFragment extends Fragment implements } } - final QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig(); + QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig(); String tagName = fragment.getClass().getSimpleName(); FragmentManager fragmentManager = provider.getContainerFragmentManager(); FragmentTransaction transaction = fragmentManager.beginTransaction() @@ -346,52 +346,7 @@ public abstract class QMUIFragment extends Fragment implements transitionConfig.popenter, transitionConfig.popout) .replace(provider.getContextViewId(), fragment, tagName); int index = transaction.commit(); - Utils.findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() { - @Override - public boolean handle(Object op) { - Field cmdField = null; - try { - cmdField = Utils.getOpCmdField(op); - cmdField.setAccessible(true); - int cmd = (int) cmdField.get(op); - if (cmd == 1) { - if (useNewTransitionConfigWhenPop) { - Field popEnterAnimField = Utils.getOpPopEnterAnimField(op); - popEnterAnimField.setAccessible(true); - popEnterAnimField.set(op, transitionConfig.popenter); - - Field popExitAnimField = Utils.getOpPopExitAnimField(op); - popExitAnimField.setAccessible(true); - popExitAnimField.set(op, transitionConfig.popout); - } - - Field oldFragmentField = Utils.getOpFragmentField(op); - oldFragmentField.setAccessible(true); - Object fragmentObj = oldFragmentField.get(op); - oldFragmentField.set(op, fragment); - Field backStackNestField = Fragment.class.getDeclaredField("mBackStackNesting"); - backStackNestField.setAccessible(true); - int oldFragmentBackStackNest = (int) backStackNestField.get(fragmentObj); - backStackNestField.set(fragment, oldFragmentBackStackNest); - backStackNestField.set(fragmentObj, --oldFragmentBackStackNest); - return true; - } - } catch (Throwable e) { - e.printStackTrace(); - } - return false; - } - - @Override - public boolean needReNameTag() { - return true; - } - - @Override - public String newTagName() { - return fragment.getClass().getSimpleName(); - } - }); + Utils.modifyOpForStartFragmentAndDestroyCurrent(fragmentManager, fragment, useNewTransitionConfigWhenPop, transitionConfig); return index; } diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragmentActivity.java b/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragmentActivity.java index 67618e1a..f71d3618 100644 --- a/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragmentActivity.java +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/QMUIFragmentActivity.java @@ -44,8 +44,6 @@ import com.qmuiteam.qmui.util.QMUIStatusBarHelper; import com.qmuiteam.qmui.util.QMUIWindowInsetHelper; import com.qmuiteam.qmui.widget.QMUIWindowInsetLayout; -import java.lang.reflect.Field; - /** * the container activity for {@link QMUIFragment}. * Created by cgspine on 15/9/14. @@ -217,67 +215,22 @@ public abstract class QMUIFragmentActivity extends InnerBaseActivity implements * @param fragment new fragment to start * @param useNewTransitionConfigWhenPop if true, use animation generated by transition C->D, * else, use animation generated by transition B->C - * - * @deprecated use {@link QMUIFragment#startFragmentAndDestroyCurrent(QMUIFragment, boolean)} */ - @Deprecated - public int startFragmentAndDestroyCurrent(final QMUIFragment fragment, final boolean useNewTransitionConfigWhenPop) { - final QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig(); - String tagName = fragment.getClass().getSimpleName(); + + public int startFragmentAndDestroyCurrent(QMUIFragment fragment, final boolean useNewTransitionConfigWhenPop) { FragmentManager fragmentManager = getSupportFragmentManager(); + if (fragmentManager.isStateSaved()) { + QMUILog.d(TAG, "startFragment can not be invoked after onSaveInstanceState"); + return -1; + } + QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig(); + String tagName = fragment.getClass().getSimpleName(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction() .setCustomAnimations(transitionConfig.enter, transitionConfig.exit, transitionConfig.popenter, transitionConfig.popout) .replace(getContextViewId(), fragment, tagName); int index = transaction.commit(); - Utils.findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() { - @Override - public boolean handle(Object op) { - Field cmdField = null; - try { - cmdField = Utils.getOpCmdField(op); - cmdField.setAccessible(true); - int cmd = (int) cmdField.get(op); - if (cmd == 1) { - if (useNewTransitionConfigWhenPop) { - Field popEnterAnimField = Utils.getOpPopEnterAnimField(op); - popEnterAnimField.setAccessible(true); - popEnterAnimField.set(op, transitionConfig.popenter); - - Field popExitAnimField = Utils.getOpPopExitAnimField(op); - popExitAnimField.setAccessible(true); - popExitAnimField.set(op, transitionConfig.popout); - } - - Field oldFragmentField = Utils.getOpFragmentField(op); - oldFragmentField.setAccessible(true); - Object fragmentObj = oldFragmentField.get(op); - oldFragmentField.set(op, fragment); - Field backStackNestField = Fragment.class.getDeclaredField("mBackStackNesting"); - backStackNestField.setAccessible(true); - int oldFragmentBackStackNest = (int) backStackNestField.get(fragmentObj); - backStackNestField.set(fragment, oldFragmentBackStackNest); - backStackNestField.set(fragmentObj, --oldFragmentBackStackNest); - return true; - } - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - return false; - } - - @Override - public boolean needReNameTag() { - return true; - } - - @Override - public String newTagName() { - return fragment.getClass().getSimpleName(); - } - }); + Utils.modifyOpForStartFragmentAndDestroyCurrent(fragmentManager, fragment, useNewTransitionConfigWhenPop, transitionConfig); return index; } @@ -286,15 +239,17 @@ public abstract class QMUIFragmentActivity extends InnerBaseActivity implements * @param fragment target fragment to start * @return commit id * - * @deprecated use {@link QMUIFragment#startFragment(QMUIFragment)} */ - @Deprecated public int startFragment(QMUIFragment fragment) { Log.i(TAG, "startFragment"); + FragmentManager fragmentManager = getSupportFragmentManager(); + if (fragmentManager.isStateSaved()) { + QMUILog.d(TAG, "startFragment can not be invoked after onSaveInstanceState"); + return -1; + } QMUIFragment.TransitionConfig transitionConfig = fragment.onFetchTransitionConfig(); String tagName = fragment.getClass().getSimpleName(); - return getSupportFragmentManager() - .beginTransaction() + return fragmentManager.beginTransaction() .setCustomAnimations(transitionConfig.enter, transitionConfig.exit, transitionConfig.popenter, transitionConfig.popout) .replace(getContextViewId(), fragment, tagName) .addToBackStack(tagName) diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/QMUISwipeBackActivityManager.java b/arch/src/main/java/com/qmuiteam/qmui/arch/QMUISwipeBackActivityManager.java index b09100a5..170406e4 100644 --- a/arch/src/main/java/com/qmuiteam/qmui/arch/QMUISwipeBackActivityManager.java +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/QMUISwipeBackActivityManager.java @@ -29,6 +29,7 @@ import java.util.Stack; public class QMUISwipeBackActivityManager implements Application.ActivityLifecycleCallbacks { private static QMUISwipeBackActivityManager sInstance; private Stack mActivityStack = new Stack<>(); + private Activity mCurrentActivity = null; @MainThread @@ -51,41 +52,45 @@ public class QMUISwipeBackActivityManager implements Application.ActivityLifecyc } @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) { mActivityStack.add(activity); } @Override - public void onActivityDestroyed(Activity activity) { + public void onActivityDestroyed(@NonNull Activity activity) { mActivityStack.remove(activity); } @Override - public void onActivityStarted(Activity activity) { + public void onActivityStarted(@NonNull Activity activity) { } @Override - public void onActivityResumed(Activity activity) { + public void onActivityResumed(@NonNull Activity activity) { + mCurrentActivity = activity; + } + + @Override + public void onActivityPaused(@NonNull Activity activity) { } @Override - public void onActivityPaused(Activity activity) { + public void onActivityStopped(@NonNull Activity activity) { } @Override - public void onActivityStopped(Activity activity) { + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { } - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - + @Nullable + public Activity getCurrentActivity(){ + return mCurrentActivity; } - /** * * refer to https://github.com/bingoogolapple/BGASwipeBackLayout-Android/ diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/Utils.java b/arch/src/main/java/com/qmuiteam/qmui/arch/Utils.java index a5e308db..4a8fb440 100644 --- a/arch/src/main/java/com/qmuiteam/qmui/arch/Utils.java +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/Utils.java @@ -29,6 +29,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -138,6 +139,58 @@ public class Utils { } } + static void modifyOpForStartFragmentAndDestroyCurrent(FragmentManager fragmentManager, + final QMUIFragment fragment, + final boolean useNewTransitionConfigWhenPop, + final QMUIFragment.TransitionConfig transitionConfig){ + findAndModifyOpInBackStackRecord(fragmentManager, -1, new Utils.OpHandler() { + @Override + public boolean handle(Object op) { + Field cmdField = null; + try { + cmdField = Utils.getOpCmdField(op); + cmdField.setAccessible(true); + int cmd = (int) cmdField.get(op); + if (cmd == 1) { + if (useNewTransitionConfigWhenPop) { + Field popEnterAnimField = Utils.getOpPopEnterAnimField(op); + popEnterAnimField.setAccessible(true); + popEnterAnimField.set(op, transitionConfig.popenter); + + Field popExitAnimField = Utils.getOpPopExitAnimField(op); + popExitAnimField.setAccessible(true); + popExitAnimField.set(op, transitionConfig.popout); + } + + Field oldFragmentField = Utils.getOpFragmentField(op); + oldFragmentField.setAccessible(true); + Object fragmentObj = oldFragmentField.get(op); + oldFragmentField.set(op, fragment); + Field backStackNestField = Fragment.class.getDeclaredField("mBackStackNesting"); + backStackNestField.setAccessible(true); + int oldFragmentBackStackNest = (int) backStackNestField.get(fragmentObj); + backStackNestField.set(fragment, oldFragmentBackStackNest); + backStackNestField.set(fragmentObj, --oldFragmentBackStackNest); + return true; + } + } catch (Throwable e) { + e.printStackTrace(); + } + return false; + } + + @Override + public boolean needReNameTag() { + return true; + } + + @Override + public String newTagName() { + return fragment.getClass().getSimpleName(); + } + }); + } + static void findAndModifyOpInBackStackRecord(FragmentManager fragmentManager, int backStackIndex, OpHandler handler) { if (fragmentManager == null || handler == null) { return; diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/ActivitySchemeItem.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/ActivitySchemeItem.java new file mode 100644 index 00000000..2f416222 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/ActivitySchemeItem.java @@ -0,0 +1,84 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.content.Intent; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qmuiteam.qmui.QMUILog; + +import java.util.HashMap; +import java.util.Map; + +class ActivitySchemeItem extends SchemeItem { + private static HashMap, QMUISchemeIntentFactory> sFactories; + + @NonNull + private final Class mActivityClass; + @NonNull + private final Class mIntentFactoryCls; + + public ActivitySchemeItem(@NonNull Class activityClass, + @Nullable Class intentFactoryCls, + @Nullable ArrayMap required, + @Nullable String[] keysForInt, + @Nullable String[] keysForBool, + @Nullable String[] keysForLong, + @Nullable String[] keysForFloat, + @Nullable String[] keysForDouble) { + super(required, keysForInt, keysForBool, keysForLong, keysForFloat, keysForDouble); + mActivityClass = activityClass; + if (intentFactoryCls == null) { + mIntentFactoryCls = QMUIDefaultSchemeIntentFactory.class; + } else { + mIntentFactoryCls = intentFactoryCls; + } + } + + @Override + public boolean handle(@NonNull Activity activity, @NonNull Map scheme) { + if (sFactories == null) { + sFactories = new HashMap<>(); + } + QMUISchemeIntentFactory factory = sFactories.get(mIntentFactoryCls); + if (factory == null) { + try { + factory = mIntentFactoryCls.newInstance(); + sFactories.put(mIntentFactoryCls, factory); + } catch (Exception e) { + QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e, + "error to instance QMUISchemeIntentFactory: %d", mIntentFactoryCls.getSimpleName()); + } + } + + if (factory != null) { + + if (factory.shouldBlockJump(activity, mActivityClass, scheme)) { + return true; + } + + Intent intent = factory.factory(activity, mActivityClass, scheme); + activity.startActivity(intent); + return true; + } + return false; + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/FragmentSchemeItem.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/FragmentSchemeItem.java new file mode 100644 index 00000000..b07ef798 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/FragmentSchemeItem.java @@ -0,0 +1,152 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.content.Intent; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qmuiteam.qmui.QMUILog; +import com.qmuiteam.qmui.arch.QMUIFragment; +import com.qmuiteam.qmui.arch.QMUIFragmentActivity; + +import java.util.HashMap; +import java.util.Map; + +class FragmentSchemeItem extends SchemeItem { + private static HashMap, QMUISchemeFragmentFactory> sFactories; + private final Class mFragmentCls; + @NonNull + private final Class[] mActivityClsList; + private final boolean mForceNewActivity; + private final String mForceNewActivityKey; + @NonNull + private final Class mFragmentFactoryCls; + + public FragmentSchemeItem(@NonNull Class fragmentCls, + @NonNull Class[] activityClsList, + @Nullable Class fragmentFactoryCls, + boolean forceNewActivity, + @Nullable String forceNewActivityKey, + @Nullable ArrayMap required, + @Nullable String[] keysForInt, + @Nullable String[] keysForBool, + @Nullable String[] keysForLong, + @Nullable String[] keysForFloat, + @Nullable String[] keysForDouble) { + super(required, keysForInt, keysForBool, keysForLong, keysForFloat, keysForDouble); + mFragmentCls = fragmentCls; + mActivityClsList = activityClsList; + mForceNewActivity = forceNewActivity; + mForceNewActivityKey = forceNewActivityKey; + if(fragmentFactoryCls == null){ + mFragmentFactoryCls = QMUIDefaultSchemeFragmentFactory.class; + }else{ + mFragmentFactoryCls = fragmentFactoryCls; + } + } + + @Override + public boolean handle(@NonNull Activity activity, @Nullable Map scheme) { + if (mActivityClsList.length == 0) { + QMUILog.d(QMUISchemeHandler.TAG, "Can not start a new fragment because the host is't provided"); + return false; + } + if (sFactories == null) { + sFactories = new HashMap<>(); + } + QMUISchemeFragmentFactory factory = sFactories.get(mFragmentFactoryCls); + if (factory == null) { + try { + factory = mFragmentFactoryCls.newInstance(); + sFactories.put(mFragmentFactoryCls, factory); + } catch (Exception e) { + QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e, + "error to instance QMUISchemeFragmentFactory: %d", mFragmentFactoryCls.getSimpleName()); + } + } + if (factory == null) { + return false; + } + + if(factory.shouldBlockJump(activity, mFragmentCls, scheme)){ + return true; + } + + if (!isCurrentActivityCanStartFragment(activity) || isForceNewActivity(scheme)) { + Intent intent = factory.factory(activity, mActivityClsList, mFragmentCls, scheme); + if (intent != null) { + activity.startActivity(intent); + return true; + } + return false; + } + + QMUIFragmentActivity fragmentActivity = (QMUIFragmentActivity) activity; + QMUIFragment fragment = factory.factory(mFragmentCls, scheme); + if (fragment != null) { + int commitId = fragmentActivity.startFragment(fragment); + if (commitId == -1) { + QMUILog.d(QMUISchemeHandler.TAG, "start fragment failed."); + return false; + } + return true; + } + return false; + } + + private boolean isCurrentActivityCanStartFragment(Activity activity) { + if (!(activity instanceof QMUIFragmentActivity)) { + return false; + } + + QMUIFragmentActivity fragmentActivity = (QMUIFragmentActivity) activity; + if (fragmentActivity.getSupportFragmentManager().isStateSaved()) { + // use new activity if the state has already been saved. + return false; + } + + for (Class aClass : mActivityClsList) { + if (aClass.isAssignableFrom(activity.getClass())) { + return true; + } + } + return false; + } + + private boolean isForceNewActivity(@Nullable Map scheme) { + if (mForceNewActivity) { + return true; + } + if (scheme == null || scheme.isEmpty()) { + return false; + } + if (scheme.get(QMUISchemeHandler.ARG_FORCE_TO_NEW_ACTIVITY) != null) { + return true; + } + + if (mForceNewActivityKey != null) { + SchemeValue schemeValue = scheme.get(mForceNewActivityKey); + return schemeValue != null && schemeValue.type == Boolean.TYPE && ((Boolean) schemeValue.value); + } + + return false; + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeFragmentFactory.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeFragmentFactory.java new file mode 100644 index 00000000..a0f755ac --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeFragmentFactory.java @@ -0,0 +1,98 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qmuiteam.qmui.QMUILog; +import com.qmuiteam.qmui.arch.QMUIFragment; +import com.qmuiteam.qmui.arch.QMUIFragmentActivity; + +import java.util.Map; + +import static com.qmuiteam.qmui.arch.scheme.QMUISchemeHandler.ARG_FROM_SCHEME; + +public class QMUIDefaultSchemeFragmentFactory implements QMUISchemeFragmentFactory { + + @Override + @Nullable + public QMUIFragment factory(@NonNull Class fragmentCls, + @Nullable Map scheme) { + try { + QMUIFragment fragment = fragmentCls.newInstance(); + fragment.setArguments(createBundleForScheme(scheme)); + return fragment; + } catch (Exception e) { + QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e, + "Error to create fragment: %s", fragmentCls.getSimpleName()); + return null; + } + } + + @Override + @Nullable + public Intent factory(@NonNull Activity activity, + @NonNull Class[] activityClassList, + @NonNull Class fragmentCls, + @Nullable Map scheme) { + Bundle bundle = createBundleForScheme(scheme); + if (activityClassList.length == 0) { + return null; + } + Intent intent = QMUIFragmentActivity.intentOf(activity, activityClassList[0], fragmentCls, bundle); + intent.putExtra(ARG_FROM_SCHEME, true); + return intent; + } + + @NonNull + private Bundle createBundleForScheme(@Nullable Map scheme) { + Bundle bundle = new Bundle(); + bundle.putBoolean(QMUISchemeHandler.ARG_FROM_SCHEME, true); + if (scheme != null && !scheme.isEmpty()) { + for (Map.Entry item : scheme.entrySet()) { + String name = item.getKey(); + SchemeValue schemeValue = item.getValue(); + if (schemeValue.type == Integer.TYPE) { + bundle.putInt(name, ((int) schemeValue.value)); + } else if (schemeValue.type == Boolean.TYPE) { + bundle.putBoolean(name, ((boolean) schemeValue.value)); + } else if (schemeValue.type == Long.TYPE) { + bundle.putLong(name, ((long) schemeValue.value)); + } else if (schemeValue.type == Float.TYPE) { + bundle.putFloat(name, ((float) schemeValue.value)); + } else if (schemeValue.type == Double.TYPE) { + bundle.putDouble(name, ((double) schemeValue.value)); + } else { + bundle.putString(name, schemeValue.origin); + } + } + } + return bundle; + } + + @Override + public boolean shouldBlockJump(@NonNull Activity activity, + @NonNull Class fragmentCls, + @Nullable Map scheme) { + return false; + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeIntentFactory.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeIntentFactory.java new file mode 100644 index 00000000..261c3dd0 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUIDefaultSchemeIntentFactory.java @@ -0,0 +1,64 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Map; + +import static com.qmuiteam.qmui.arch.scheme.QMUISchemeHandler.ARG_FROM_SCHEME; + +public class QMUIDefaultSchemeIntentFactory implements QMUISchemeIntentFactory { + @Override + public Intent factory(@NonNull Activity activity, + @NonNull Class activityClass, + @Nullable Map scheme) { + Intent intent = new Intent(activity, activityClass); + intent.putExtra(ARG_FROM_SCHEME, true); + if (scheme != null && !scheme.isEmpty()) { + for (Map.Entry item : scheme.entrySet()) { + String name = item.getKey(); + SchemeValue schemeValue = item.getValue(); + if (schemeValue.type == Integer.TYPE) { + intent.putExtra(name, ((int) schemeValue.value)); + } else if (schemeValue.type == Boolean.TYPE) { + intent.putExtra(name, ((boolean) schemeValue.value)); + } else if (schemeValue.type == Long.TYPE) { + intent.putExtra(name, ((long) schemeValue.value)); + } else if (schemeValue.type == Float.TYPE) { + intent.putExtra(name, ((float) schemeValue.value)); + } else if (schemeValue.type == Double.TYPE) { + intent.putExtra(name, ((double) schemeValue.value)); + } else { + intent.putExtra(name, schemeValue.origin); + } + } + } + return intent; + } + + @Override + public boolean shouldBlockJump(@NonNull Activity activity, + @NonNull Class activityClass, + @Nullable Map scheme) { + return false; + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeFragmentFactory.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeFragmentFactory.java new file mode 100644 index 00000000..31f7565d --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeFragmentFactory.java @@ -0,0 +1,44 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qmuiteam.qmui.arch.QMUIFragment; +import com.qmuiteam.qmui.arch.QMUIFragmentActivity; + +import java.util.Map; + +public interface QMUISchemeFragmentFactory { + @Nullable + QMUIFragment factory(@NonNull Class fragmentCls, + @Nullable Map scheme); + + @Nullable + Intent factory(@NonNull Activity activity, + @NonNull Class[] activityClassList, + @NonNull Class fragmentCls, + @Nullable Map scheme); + + boolean shouldBlockJump(@NonNull Activity activity, + @NonNull Class fragmentCls, + @Nullable Map scheme); +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandleInterpolator.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandleInterpolator.java new file mode 100644 index 00000000..7fe61c98 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandleInterpolator.java @@ -0,0 +1,25 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; + +import java.util.Map; + +public interface QMUISchemeHandleInterpolator { + boolean intercept(Activity activity, String action, Map params, String origin); +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandler.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandler.java new file mode 100644 index 00000000..76178302 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeHandler.java @@ -0,0 +1,202 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.util.Pair; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qmuiteam.qmui.arch.QMUISwipeBackActivityManager; +import com.qmuiteam.qmui.arch.record.QMUILatestVisitStorage; +import com.qmuiteam.qmui.arch.record.RecordIdClassMap; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class QMUISchemeHandler { + static final String TAG = "QMUISchemeHandler"; + public static String ARG_FROM_SCHEME = "__qmui_arg_from_scheme"; + public static String ARG_FORCE_TO_NEW_ACTIVITY = "__qmui_force_to_new_activity"; + + private static SchemeMap sSchemeMap; + + static { + try { + Class cls = Class.forName(SchemeMap.class.getCanonicalName() + "Impl"); + sSchemeMap = (SchemeMap) cls.newInstance(); + } catch (ClassNotFoundException e) { + sSchemeMap = new SchemeMap() { + @Nullable + @Override + public SchemeItem findScheme(@NonNull String schemeName, @Nullable Map params) { + return null; + } + + @Override + public boolean exists(@NonNull String schemeName) { + return false; + } + }; + } catch (IllegalAccessException e) { + throw new RuntimeException("Can not access the Class SchemeMapImpl. " + + "Please file a issue to report this."); + } catch (InstantiationException e) { + throw new RuntimeException("Can not instance the Class SchemeMapImpl. " + + "Please file a issue to report this."); + } + } + + + private final String mPrefix; + private final List mInterpolatorList; + private final long mBlockSameSchemeTimeout; + private String mLastHandledScheme = null; + private long mLastSchemeHandledTime = 0; + + private QMUISchemeHandler(Builder builder) { + mPrefix = builder.mPrefix; + List interpolatorList = builder.mInterpolatorList; + if(interpolatorList != null && !interpolatorList.isEmpty()){ + mInterpolatorList = new ArrayList<>(interpolatorList); + }else{ + mInterpolatorList = null; + } + mBlockSameSchemeTimeout = builder.mBlockSameSchemeTimeout; + } + + public boolean handle(String scheme){ + if(scheme == null || !scheme.startsWith(mPrefix)){ + return false; + } + + if(scheme.equals(mLastHandledScheme) && System.currentTimeMillis() - mLastSchemeHandledTime < mBlockSameSchemeTimeout){ + return true; + } + + Activity currentActivity = QMUISwipeBackActivityManager.getInstance().getCurrentActivity(); + if(currentActivity == null){ + return false; + } + + scheme = scheme.substring(mPrefix.length()); + String[] elements = scheme.split("\\?"); + if(elements.length == 0 || elements[0] == null || elements[0].isEmpty()){ + return false; + } + String action = elements[0]; + if(!sSchemeMap.exists(action)){ + return false; + } + + Map params; + if(elements.length < 2){ + params = null; + }else{ + params = parseParams(elements[1]); + } + + boolean handled = false; + if(mInterpolatorList != null && !mInterpolatorList.isEmpty()){ + for(QMUISchemeHandleInterpolator interpolator: mInterpolatorList){ + if(interpolator.intercept(currentActivity, action, params, scheme)){ + handled = true; + break; + } + } + } + + if(!handled){ + SchemeItem schemeItem = sSchemeMap.findScheme(action, params); + if(schemeItem != null){ + handled = schemeItem.handle(currentActivity, schemeItem.convertFrom(params)); + } + } + + if(handled){ + mLastHandledScheme = scheme; + mLastSchemeHandledTime = System.currentTimeMillis(); + } + + return handled; + } + + @Nullable + public Map parseParams(@Nullable String schemeParams) { + if (schemeParams == null || schemeParams.isEmpty()) { + return null; + } + + Map queryMap = new HashMap<>(); + int start = 0; + do { + int next = schemeParams.indexOf('&', start); + int end = (next == -1) ? schemeParams.length() : next; + if (start == end) { + start += 1; + continue; + } + + int separator = schemeParams.indexOf('=', start); + if (separator > end || separator == -1) { + separator = end; + } + if (separator == start) { + start = end + 1; + continue; + } + + String name = schemeParams.substring(start, separator); + String value = separator == end ? "" : schemeParams.substring(separator + 1, end); + queryMap.put(name, value); + start = end + 1; + } while (start < schemeParams.length()); + return queryMap; + } + + + public static class Builder { + public static final long BLOCK_SAME_SCHEME_DEFAULT_TIMEOUT = 500; + private String mPrefix; + private List mInterpolatorList; + private long mBlockSameSchemeTimeout = BLOCK_SAME_SCHEME_DEFAULT_TIMEOUT; + + public Builder(@NonNull String prefix) { + mPrefix = prefix; + } + + public Builder addInterpolator(QMUISchemeHandleInterpolator interpolator) { + if(mInterpolatorList == null){ + mInterpolatorList = new ArrayList<>(); + } + mInterpolatorList.add(interpolator); + return this; + } + + public Builder setBlockSameSchemeTimeout(long blockSameSchemeTimeout) { + mBlockSameSchemeTimeout = blockSameSchemeTimeout; + return this; + } + + public QMUISchemeHandler build() { + return new QMUISchemeHandler(this); + } + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeIntentFactory.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeIntentFactory.java new file mode 100644 index 00000000..147a4217 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeIntentFactory.java @@ -0,0 +1,35 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Map; + +public interface QMUISchemeIntentFactory { + Intent factory(@NonNull Activity activity, + @NonNull Class activityClass, + @Nullable Map scheme); + + boolean shouldBlockJump(@NonNull Activity activity, + @NonNull Class activityClass, + @Nullable Map scheme); +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeParamValueDecoder.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeParamValueDecoder.java new file mode 100644 index 00000000..6bde7fcd --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/QMUISchemeParamValueDecoder.java @@ -0,0 +1,38 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.net.Uri; + +import java.util.Map; + +public class QMUISchemeParamValueDecoder implements QMUISchemeHandleInterpolator { + + @Override + public boolean intercept(Activity activity, String action, Map params, String origin) { + if (params != null) { + for (String key : params.keySet()) { + String oldValue = params.get(key); + if (oldValue != null && oldValue.length() > 0) { + params.put(key, Uri.decode(oldValue)); + } + } + } + return false; + } +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeItem.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeItem.java new file mode 100644 index 00000000..575b7a12 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeItem.java @@ -0,0 +1,132 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.qmuiteam.qmui.arch.scheme; + +import android.app.Activity; +import android.util.ArrayMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.qmuiteam.qmui.QMUILog; + +import java.util.HashMap; +import java.util.Map; + +public abstract class SchemeItem { + @Nullable + private ArrayMap mRequired; + @Nullable + private String[] mKeysForInt; + @Nullable + private String[] mKeysForBool; + @Nullable + private String[] mKeysForLong; + @Nullable + private String[] mKeysForFloat; + @Nullable + private String[] mKeysForDouble; + + public SchemeItem(@Nullable ArrayMap required, + @Nullable String[] keysForInt, + @Nullable String[] keysForBool, + @Nullable String[] keysForLong, + @Nullable String[] keysForFloat, + @Nullable String[] keysForDouble) { + mRequired = required; + mKeysForInt = keysForInt; + mKeysForBool = keysForBool; + mKeysForLong = keysForLong; + mKeysForFloat = keysForFloat; + mKeysForDouble = keysForDouble; + } + + @Nullable + public Map convertFrom(@Nullable Map schemeParams) { + if (schemeParams == null || schemeParams.isEmpty()) { + return null; + } + + Map queryMap = new HashMap<>(); + for(Map.Entry param: schemeParams.entrySet()){ + String name = param.getKey(); + String value = param.getValue(); + if(name == null || name.isEmpty()){ + continue; + } + try { + if (contains(mKeysForInt, name)) { + queryMap.put(name, new SchemeValue(value, Integer.valueOf(value), Integer.TYPE)); + } else if (QMUISchemeHandler.ARG_FORCE_TO_NEW_ACTIVITY.equals(name) || contains(mKeysForBool, name)) { + boolean isFalse = "0".equals(value) || "false".equals(value.toLowerCase()); + queryMap.put(name, new SchemeValue(value, !isFalse, Boolean.TYPE)); + } else if (contains(mKeysForLong, name)) { + queryMap.put(name, new SchemeValue(value, Long.valueOf(value), Long.TYPE)); + } else if (contains(mKeysForFloat, name)) { + queryMap.put(name, new SchemeValue(value, Float.valueOf(value), Float.TYPE)); + } else if (contains(mKeysForDouble, name)) { + queryMap.put(name, new SchemeValue(value, Double.valueOf(value), Double.TYPE)); + } else { + queryMap.put(name, new SchemeValue(value, value, String.class)); + } + } catch (Exception e) { + QMUILog.printErrStackTrace(QMUISchemeHandler.TAG, e, + "error to parse scheme param: %s = %s", name, value); + } + } + return queryMap; + } + + private static boolean contains(@Nullable String[] array, @NonNull String key) { + if (array == null || array.length == 0) { + return false; + } + for (int i = 0; i < array.length; i++) { + if (key.equals(array[i])) { + return true; + } + } + return false; + } + + // used by generated code(SchemeMapImpl) + boolean match(@Nullable Map scheme) { + if (mRequired == null || mRequired.isEmpty()) { + return true; + } + if (scheme == null || scheme.isEmpty()) { + return false; + } + for (int i = 0; i < mRequired.size(); i++) { + String key = mRequired.keyAt(i); + if(!scheme.containsKey(key)){ + return false; + } + String value = mRequired.valueAt(i); + if(value == null){ + // if no value. that means scheme must provide this key. + continue; + } + String actual = scheme.get(key); + if (actual == null || !actual.equals(value)) { + return false; + } + } + return true; + } + + public abstract boolean handle(@NonNull Activity activity, @Nullable Map scheme); +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeMap.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeMap.java new file mode 100644 index 00000000..c33243b6 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeMap.java @@ -0,0 +1,30 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmui.arch.scheme; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Map; + +public interface SchemeMap { + + @Nullable + SchemeItem findScheme(@NonNull String schemeAction, @Nullable Map params); + + boolean exists(@NonNull String schemeAction); +} diff --git a/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeValue.java b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeValue.java new file mode 100644 index 00000000..a4bcbfc5 --- /dev/null +++ b/arch/src/main/java/com/qmuiteam/qmui/arch/scheme/SchemeValue.java @@ -0,0 +1,30 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.qmuiteam.qmui.arch.scheme; + +import androidx.annotation.NonNull; + +public class SchemeValue { + final String origin; + final Object value; + final Class type; + + public SchemeValue(@NonNull String origin, Object value, Class type) { + this.origin = origin; + this.value = value; + this.type = type; + } +} diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/ArchTestActivity.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/ArchTestActivity.java index 081c455f..3693f541 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/ArchTestActivity.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/ArchTestActivity.java @@ -22,6 +22,7 @@ import androidx.core.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; +import com.qmuiteam.qmui.arch.annotation.ActivityScheme; import com.qmuiteam.qmui.widget.QMUITopBarLayout; import com.qmuiteam.qmuidemo.R; import com.qmuiteam.qmuidemo.base.BaseActivity; @@ -30,6 +31,7 @@ import com.qmuiteam.qmuidemo.fragment.lab.QDArchTestFragment; import butterknife.BindView; import butterknife.ButterKnife; +@ActivityScheme(name = "arch", required = {"aa", "bb=3"}, keysWithBoolValue = {"aa"}) public class ArchTestActivity extends BaseActivity { @BindView(R.id.topbar) QMUITopBarLayout mTopBar; diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/LauncherActivity.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/LauncherActivity.java index 02a0fed2..67d95f4a 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/LauncherActivity.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/activity/LauncherActivity.java @@ -23,18 +23,20 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.widget.Toast; -import com.qmuiteam.qmui.arch.QMUILatestVisit; -import com.qmuiteam.qmuidemo.QDMainActivity; - import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import com.qmuiteam.qmui.arch.QMUILatestVisit; +import com.qmuiteam.qmui.arch.annotation.ActivityScheme; +import com.qmuiteam.qmuidemo.QDMainActivity; + /** * @author cginechen * @date 2016-12-08 */ +@ActivityScheme(name = "launcher") public class LauncherActivity extends Activity { private static final int PERMISSIONS_REQUEST_CODE = 10; @@ -79,9 +81,9 @@ public class LauncherActivity extends Activity { } - private boolean allPermissionsGranted(){ - for(String permission: PERMISSIONS_REQUIRED){ - if(ContextCompat.checkSelfPermission(getBaseContext(), permission) != PackageManager.PERMISSION_GRANTED){ + private boolean allPermissionsGranted() { + for (String permission : PERMISSIONS_REQUIRED) { + if (ContextCompat.checkSelfPermission(getBaseContext(), permission) != PackageManager.PERMISSION_GRANTED) { return false; } } diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFixModeFragment.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFixModeFragment.java index ce82eb84..ae6d1db2 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFixModeFragment.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFixModeFragment.java @@ -24,6 +24,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import com.qmuiteam.qmui.arch.annotation.FragmentScheme; import com.qmuiteam.qmui.arch.annotation.LatestVisitRecord; import com.qmuiteam.qmui.util.QMUIDisplayHelper; import com.qmuiteam.qmui.util.QMUIResHelper; @@ -33,6 +34,7 @@ import com.qmuiteam.qmui.widget.tab.QMUITab; import com.qmuiteam.qmui.widget.tab.QMUITabBuilder; import com.qmuiteam.qmui.widget.tab.QMUITabIndicator; import com.qmuiteam.qmui.widget.tab.QMUITabSegment; +import com.qmuiteam.qmuidemo.QDMainActivity; import com.qmuiteam.qmuidemo.R; import com.qmuiteam.qmuidemo.base.BaseFragment; import com.qmuiteam.qmuidemo.lib.Group; @@ -56,6 +58,11 @@ import butterknife.ButterKnife; @LatestVisitRecord @Widget(group = Group.Other, name = "固定宽度,内容均分") +@FragmentScheme( + name = "tab", + activities = {QDMainActivity.class}, + required = {"mode=1"}, + keysWithIntValue = {"mode"}) public class QDTabSegmentFixModeFragment extends BaseFragment { @BindView(R.id.topbar) QMUITopBarLayout mTopBar; diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFragment.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFragment.java index 9abc8b8b..9ae19948 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFragment.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentFragment.java @@ -19,9 +19,11 @@ package com.qmuiteam.qmuidemo.fragment.components; import android.view.LayoutInflater; import android.view.View; +import com.qmuiteam.qmui.arch.annotation.FragmentScheme; import com.qmuiteam.qmui.widget.QMUITopBarLayout; import com.qmuiteam.qmui.widget.grouplist.QMUIGroupListView; import com.qmuiteam.qmui.widget.tab.QMUITabSegment; +import com.qmuiteam.qmuidemo.QDMainActivity; import com.qmuiteam.qmuidemo.R; import com.qmuiteam.qmuidemo.base.BaseFragment; import com.qmuiteam.qmuidemo.lib.annotation.Widget; @@ -36,6 +38,7 @@ import butterknife.ButterKnife; * @date 2016-10-21 */ +@FragmentScheme(name = "tab", activities = {QDMainActivity.class}) @Widget(widgetClass = QMUITabSegment.class, iconRes = R.mipmap.icon_grid_tab_segment) public class QDTabSegmentFragment extends BaseFragment { diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentScrollableModeFragment.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentScrollableModeFragment.java index 4c59569b..17a0938c 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentScrollableModeFragment.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentScrollableModeFragment.java @@ -31,6 +31,7 @@ import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; +import com.qmuiteam.qmui.arch.annotation.FragmentScheme; import com.qmuiteam.qmui.skin.QMUISkinHelper; import com.qmuiteam.qmui.skin.QMUISkinValueBuilder; import com.qmuiteam.qmui.skin.SkinWriter; @@ -40,6 +41,7 @@ import com.qmuiteam.qmui.widget.tab.QMUITabBuilder; import com.qmuiteam.qmui.widget.tab.QMUITabIndicator; import com.qmuiteam.qmui.widget.tab.QMUITabSegment; import com.qmuiteam.qmui.widget.QMUITopBarLayout; +import com.qmuiteam.qmuidemo.QDMainActivity; import com.qmuiteam.qmuidemo.R; import com.qmuiteam.qmuidemo.base.BaseFragment; import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDLazyTestObserver; @@ -55,6 +57,11 @@ import butterknife.BindView; import butterknife.ButterKnife; @Widget(group = Group.Other, name = "内容自适应,超过父容器则滚动") +@FragmentScheme( + name = "tab", + activities = {QDMainActivity.class}, + required = {"mode=2"}, + keysWithIntValue = {"mode"}) public class QDTabSegmentScrollableModeFragment extends BaseFragment { @SuppressWarnings("FieldCanBeLocal") private final int TAB_COUNT = 10; diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentSpaceWeightFragment.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentSpaceWeightFragment.java index f33a3fcc..50b71fd1 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentSpaceWeightFragment.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/components/QDTabSegmentSpaceWeightFragment.java @@ -25,12 +25,14 @@ import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; +import com.qmuiteam.qmui.arch.annotation.FragmentScheme; import com.qmuiteam.qmui.util.QMUIDisplayHelper; import com.qmuiteam.qmui.widget.QMUITopBarLayout; import com.qmuiteam.qmui.widget.tab.QMUITab; import com.qmuiteam.qmui.widget.tab.QMUITabBuilder; import com.qmuiteam.qmui.widget.tab.QMUITabIndicator; import com.qmuiteam.qmui.widget.tab.QMUITabSegment; +import com.qmuiteam.qmuidemo.QDMainActivity; import com.qmuiteam.qmuidemo.R; import com.qmuiteam.qmuidemo.base.BaseFragment; import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDLazyTestObserver; @@ -56,6 +58,11 @@ import butterknife.ButterKnife; */ @Widget(group = Group.Other, name = "内容自适应,添加weight控制间距") +@FragmentScheme( + name = "tab", + activities = {QDMainActivity.class}, + required = {"mode=3"}, + keysWithIntValue = {"mode"}) public class QDTabSegmentSpaceWeightFragment extends BaseFragment { @SuppressWarnings("FieldCanBeLocal") private final int TAB_COUNT = 3; diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/lab/QDSchemeFragment.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/lab/QDSchemeFragment.java new file mode 100644 index 00000000..a1d6c286 --- /dev/null +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/fragment/lab/QDSchemeFragment.java @@ -0,0 +1,99 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmuidemo.fragment.lab; + +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; + +import com.qmuiteam.qmui.arch.annotation.LatestVisitRecord; +import com.qmuiteam.qmui.widget.QMUITopBarLayout; +import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; +import com.qmuiteam.qmuidemo.R; +import com.qmuiteam.qmuidemo.base.BaseFragment; +import com.qmuiteam.qmuidemo.lib.Group; +import com.qmuiteam.qmuidemo.lib.annotation.Widget; +import com.qmuiteam.qmuidemo.manager.QDDataManager; +import com.qmuiteam.qmuidemo.manager.QDSchemeManager; + +import butterknife.BindView; +import butterknife.ButterKnife; + +@LatestVisitRecord +@Widget(group = Group.Lab, name = "Scheme", iconRes = R.mipmap.icon_grid_in_progress) +public class QDSchemeFragment extends BaseFragment { + + @BindView(R.id.topbar) + QMUITopBarLayout mTopBar; + + @BindView(R.id.edit_text) + EditText mEditText; + + @BindView(R.id.button) + QMUIRoundButton mBtn; + + @Override + protected View onCreateView() { + View view = LayoutInflater.from(getContext()).inflate(R.layout.fragment_scheme, null); + ButterKnife.bind(this, view); + initTopBar(); + initEditStuff(); + return view; + } + + private void initTopBar() { + mTopBar.addLeftBackImageButton().setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + popBackStack(); + } + }); + + mTopBar.setTitle(QDDataManager.getInstance().getDescription(this.getClass()).getName()); + } + + private void initEditStuff() { + mBtn.setEnabled(false); + mEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (s == null || s.length() == 0) { + mBtn.setEnabled(false); + } + mBtn.setEnabled(true); + } + }); + mBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + QDSchemeManager.getInstance().handle(mEditText.getText().toString()); + } + }); + } +} diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDDataManager.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDDataManager.java index 269fc4c7..66735d42 100644 --- a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDDataManager.java +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDDataManager.java @@ -46,6 +46,7 @@ import com.qmuiteam.qmuidemo.fragment.components.viewpager.QDViewPagerFragment; import com.qmuiteam.qmuidemo.fragment.lab.QDAnimationListViewFragment; import com.qmuiteam.qmuidemo.fragment.lab.QDArchTestFragment; import com.qmuiteam.qmuidemo.fragment.lab.QDContinuousNestedScrollFragment; +import com.qmuiteam.qmuidemo.fragment.lab.QDSchemeFragment; import com.qmuiteam.qmuidemo.fragment.lab.QDSnapHelperFragment; import com.qmuiteam.qmuidemo.fragment.lab.QDWebViewFragment; import com.qmuiteam.qmuidemo.fragment.util.QDColorHelperFragment; @@ -144,6 +145,7 @@ public class QDDataManager { mLabNames.add(QDSnapHelperFragment.class); mLabNames.add(QDArchTestFragment.class); mLabNames.add(QDWebViewFragment.class); + mLabNames.add(QDSchemeFragment.class); } public QDItemDescription getDescription(Class cls) { diff --git a/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDSchemeManager.java b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDSchemeManager.java new file mode 100644 index 00000000..0881d664 --- /dev/null +++ b/qmuidemo/src/main/java/com/qmuiteam/qmuidemo/manager/QDSchemeManager.java @@ -0,0 +1,68 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is + * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.qmuiteam.qmuidemo.manager; + +import android.app.Activity; +import android.util.Log; +import android.widget.Toast; + +import com.qmuiteam.qmui.arch.QMUISwipeBackActivityManager; +import com.qmuiteam.qmui.arch.scheme.QMUISchemeHandleInterpolator; +import com.qmuiteam.qmui.arch.scheme.QMUISchemeHandler; +import com.qmuiteam.qmui.arch.scheme.QMUISchemeParamValueDecoder; + +import java.util.Map; + +public class QDSchemeManager { + private static final String TAG = "QDSchemeManager"; + public static final String SCHEME_PREFIX = "qmui://"; + private static QDSchemeManager sInstance; + + public static QDSchemeManager getInstance() { + if (sInstance == null) { + sInstance = new QDSchemeManager(); + } + return sInstance; + } + + private QMUISchemeHandler mSchemeHandler; + + private QDSchemeManager() { + mSchemeHandler = new QMUISchemeHandler.Builder(SCHEME_PREFIX) + .setBlockSameSchemeTimeout(1000) + .addInterpolator(new QMUISchemeHandleInterpolator() { + @Override + public boolean intercept(Activity activity, String action, Map params, String origin) { + // Log the scheme. + Log.i(TAG, "handle scheme: " + origin); + return false; + } + }) + .addInterpolator(new QMUISchemeParamValueDecoder()) + .build(); + } + + public boolean handle(String scheme) { + if (!mSchemeHandler.handle(scheme)) { + Log.i(TAG, "scheme can not be handled: " + scheme); + Toast.makeText(QMUISwipeBackActivityManager.getInstance().getCurrentActivity(), + "scheme can not be handled: " + scheme, Toast.LENGTH_SHORT).show(); + return false; + } + return true; + } +} diff --git a/qmuidemo/src/main/res/layout/fragment_scheme.xml b/qmuidemo/src/main/res/layout/fragment_scheme.xml new file mode 100644 index 00000000..df7b57d2 --- /dev/null +++ b/qmuidemo/src/main/res/layout/fragment_scheme.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + \ No newline at end of file